From f1ce646f00e729d199e12aa055d04eceb8da3dca Mon Sep 17 00:00:00 2001 From: Ameya Gurjar Date: Mon, 2 Sep 2024 14:47:20 -0700 Subject: [PATCH 01/45] added modulo op --- .../dsp/SimpleBlocks/include/toy/Ops.td | 20 +++++++++++++++++++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 12 +++++++++++ .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 3 ++- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 10 ++++++++++ .../Examples/DspExample/dsp_modulo_test.toy | 16 +++++++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 mlir/test/Examples/DspExample/dsp_modulo_test.toy diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 6d714f9832b6..7daa15420eea 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -92,6 +92,26 @@ def ConstantOp : Dsp_Op<"constant", [Pure]> { let hasVerifier = 1; } +//===----------------------------------------------------------------------===// +// ModuloOp +//===----------------------------------------------------------------------===// + +def ModuloOp : Dsp_Op<"modulo", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "element-wise modulo operation"; + let description = [{ + The "modulo" operation performs element-wise modulo op between two tensors. + The shapes of the tensor operands are expected to match. + }]; + + let arguments = (ins F64Tensor:$lhs, F64Tensor:$rhs); + let results = (outs F64Tensor); + + let builders = [ + OpBuilder<(ins "Value":$lhs, "Value":$rhs)> + ]; +} + //===----------------------------------------------------------------------===// // AddOp //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index 47f76a2ff96c..f6b8f4f3f4bf 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -241,6 +241,18 @@ mlir::LogicalResult ConstantOp::verify() { return mlir::success(); } +//===----------------------------------------------------------------------===// +// ModuloOp +//===----------------------------------------------------------------------===// + +void ModuloOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs, mlir::Value rhs) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({lhs, rhs}); +} + +void ModuloOp::inferShapes() { getResult().setType(getLhs().getType()); } + //===----------------------------------------------------------------------===// // AddOp //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 537989becb84..5e47ebf8fdd9 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -6046,6 +6046,7 @@ struct BinaryOpLowering : public ConversionPattern { }; using AddOpLowering = BinaryOpLowering; +using ModuloOpLowering = BinaryOpLowering; using SubOpLowering = BinaryOpLowering; using MulOpLowering = BinaryOpLowering; using DivOpLowering = BinaryOpLowering; @@ -6271,7 +6272,7 @@ void ToyToAffineLoweringPass::runOnOperation() { // Now that the conversion target has been defined, we just need to provide // the set of patterns that will lower the Toy operations. RewritePatternSet patterns(&getContext()); - patterns.add(location, operands[0], operands[1]); } + // Modulo Op + if(callee == "modulo"){ + if(call.getArgs().size() != 2){ + emitError(location, "MLIR codegen encountered an error: dsp.sub " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); + } + if(callee == "zeroCrossCount"){ if(call.getArgs().size() != 1){ emitError(location, "MLIR codegen encountered an error: dsp.zeroCrossCount " diff --git a/mlir/test/Examples/DspExample/dsp_modulo_test.toy b/mlir/test/Examples/DspExample/dsp_modulo_test.toy new file mode 100644 index 000000000000..dcdb20bbaeae --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_modulo_test.toy @@ -0,0 +1,16 @@ +def main() { + var a = [10,20,30]; + var b = [40,50,60]; + var c = modulo(a, b); + print(c); +} + +#module { +# dsp.func @main() { +# %0 = dsp.constant dense<[1.000000e+01, 2.000000e+01, 3.000000e+01]> : tensor<3xf64> +# %1 = dsp.constant dense<[4.000000e+01, 5.000000e+01, 6.000000e+01]> : tensor<3xf64> +# %2 = "dsp.modulo"(%0, %1) : (tensor<3xf64>, tensor<3xf64>) -> tensor<*xf64> +# dsp.print %2 : tensor<*xf64> +# dsp.return +# } +#} \ No newline at end of file From 0f6a518aae0bd7e338d4d755ed717a80624ecb98 Mon Sep 17 00:00:00 2001 From: Atharva Khedkar <55466743+AtharvaKhedkar@users.noreply.github.com> Date: Tue, 3 Sep 2024 20:20:26 -0700 Subject: [PATCH 02/45] Update README.md --- README.md | 67 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index a9b29ecbc1a3..bba0b2efcf8c 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,47 @@ -# The LLVM Compiler Infrastructure +# DSP-MLIR Compiler -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/llvm/llvm-project/badge)](https://securityscorecards.dev/viewer/?uri=github.com/llvm/llvm-project) -[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8273/badge)](https://www.bestpractices.dev/projects/8273) -[![libc++](https://github.com/llvm/llvm-project/actions/workflows/libcxx-build-and-test.yaml/badge.svg?branch=main&event=schedule)](https://github.com/llvm/llvm-project/actions/workflows/libcxx-build-and-test.yaml?query=event%3Aschedule) +This repository contains the source code for **DSP-MLIR**, a compiler tailored for Digital Signal Processing (DSP) applications. It provides highly optimized tools and environments for building, optimizing, and running DSP operations like Fast Fourier Transforms (FFT), Finite Impulse Response (FIR) filters, and more. -Welcome to the LLVM project! +The project is built on top of the **LLVM** infrastructure and leverages the **MLIR** (Multi-Level Intermediate Representation) framework for implementing DSP-specific operations and transformations. -This repository contains the source code for LLVM, a toolkit for the -construction of highly optimized compilers, optimizers, and run-time -environments. -The LLVM project has multiple components. The core of the project is -itself called "LLVM". This contains all of the tools, libraries, and header -files needed to process intermediate representations and convert them into -object files. Tools include an assembler, disassembler, bitcode analyzer, and -bitcode optimizer. -C-like languages use the [Clang](https://clang.llvm.org/) frontend. This -component compiles C, C++, Objective-C, and Objective-C++ code into LLVM bitcode --- and from there into object files, using LLVM. -Other components include: -the [libc++ C++ standard library](https://libcxx.llvm.org), -the [LLD linker](https://lld.llvm.org), and more. +## Build Instructions -## Getting the Source Code and Building LLVM +To build the DSP-MLIR compiler, follow these steps: -Consult the -[Getting Started with LLVM](https://llvm.org/docs/GettingStarted.html#getting-the-source-code-and-building-llvm) -page for information on building and running LLVM. +### Step 1: Clone this repository and cd into the DSP-MLIR folder. -For information on how to contribute to the LLVM project, please take a look at -the [Contributing to LLVM](https://llvm.org/docs/Contributing.html) guide. -## Getting in touch +### Step 2: Make and cd into the build directory using the following command: -Join the [LLVM Discourse forums](https://discourse.llvm.org/), [Discord -chat](https://discord.gg/xS7Z362), -[LLVM Office Hours](https://llvm.org/docs/GettingInvolved.html#office-hours) or -[Regular sync-ups](https://llvm.org/docs/GettingInvolved.html#online-sync-ups). +```bash +mkdir build +cd build + +``` +### Step 3: To build the project, run the following command: +```bash +cmake -G Ninja ../llvm \ + -DLLVM_ENABLE_PROJECTS=mlir \ + -DLLVM_BUILD_EXAMPLES=ON \ + -DLLVM_TARGETS_TO_BUILD="Native" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON +``` + +### Step 4: After configuring the build, compile the project by running: +```bash +ninja +``` + +## Running an Example + +After the build completes, you can run an example to test the DSP operations. From the build directory: + +```bash +ninja && ./bin/dsp1 ../mlir/test/Examples/DspExample/dsp_gain_op.py -emit=mlir-affine +ninja && ./bin/dsp1 ../mlir/test/Examples/DspExample/dsp_gain_op.py -emit=jit +``` -The LLVM project has adopted a [code of conduct](https://llvm.org/docs/CodeOfConduct.html) for -participants to all modes of communication within the project. From 308096d24db6d56a3b41890046a4215f4796553a Mon Sep 17 00:00:00 2001 From: megakuo Date: Thu, 5 Sep 2024 07:32:10 +0000 Subject: [PATCH 03/45] support negative signed tensor --- .../dsp/SimpleBlocks/include/toy/Lexer.h | 2 +- .../test/Examples/DspExample/dsp_neg_input.py | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 mlir/test/Examples/DspExample/dsp_neg_input.py diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Lexer.h b/mlir/examples/dsp/SimpleBlocks/include/toy/Lexer.h index d6bc5443fb30..94469b29b103 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Lexer.h +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Lexer.h @@ -149,7 +149,7 @@ class Lexer { } // Number: [0-9.]+ - if (isdigit(lastChar) || lastChar == '.') { + if (lastChar == '-' || isdigit(lastChar) || lastChar == '.') { std::string numStr; do { numStr += lastChar; diff --git a/mlir/test/Examples/DspExample/dsp_neg_input.py b/mlir/test/Examples/DspExample/dsp_neg_input.py new file mode 100644 index 000000000000..cbb9fdee0a42 --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_neg_input.py @@ -0,0 +1,26 @@ +# RUN: toyc-ch2 %s -emit=mlir 2>&1 | FileCheck %s + +def main() { + # var a = [10,20,30]; + # var b = [40,50,60]; + # var a = [[10,20],[30,40]]; + # var b = [[40,50],[60,70]]; + + # var a = [[[10,20],[30,40]] , [[10,20],[30,40]]]; + # var b = [[[40,50],[60,70]] , [[0,0],[10,20]]]; + var a = [[[10,-20],[30.9,0]] ]; + var b = [[[40,50],[-60,70]] ]; + var c = sub(a, b); + print(c); +} +# /home/local/ASUAD/apkhedka/ForLLVM/build/bin/dsp1 /home/local/ASUAD/apkhedka/ForLLVM/mlir/test/Examples/DspExample/dsp_sub_op.py -emit=mlir + +# module { +# dsp.func @main() { +# %0 = dsp.constant dense<[1.000000e+01, 2.000000e+01, 3.000000e+01]> : tensor<3xf64> +# %1 = dsp.constant dense<[4.000000e+01, 5.000000e+01, 6.000000e+01]> : tensor<3xf64> +# %2 = "dsp.sub"(%0, %1) : (tensor<3xf64>, tensor<3xf64>) -> tensor<*xf64> +# dsp.print %2 : tensor<*xf64> +# dsp.return +# } +# } From c19e20bccf2e20df45a0432df614ac688a78c365 Mon Sep 17 00:00:00 2001 From: Ameya Gurjar Date: Fri, 11 Oct 2024 18:13:14 +0000 Subject: [PATCH 04/45] fft --- .../dsp/SimpleBlocks/include/toy/Ops.td | 36 ++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 24 ++ .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 315 +++++++++++++++++- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 26 +- mlir/test/Examples/DspExample/dsp_fft.py | 7 + mlir/test/Examples/DspExample/fft.mlir | 189 +++++++++++ 6 files changed, 592 insertions(+), 5 deletions(-) create mode 100644 mlir/test/Examples/DspExample/dsp_fft.py create mode 100644 mlir/test/Examples/DspExample/fft.mlir diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 7daa15420eea..1af513274e4c 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -536,6 +536,42 @@ def SubOp : Dsp_Op<"sub", [Pure , DeclareOpInterfaceMethods]> { + let summary = "Performs FFT Operation on the input"; + let description = [{ + Takes the input array and returns the real part of the fourier transform, an array of the same size. + }]; + + let arguments = (ins F64Tensor:$lhs); + let results = (outs F64Tensor); + + let builders = [ + OpBuilder<(ins "Value":$lhs)> + ]; +} + +//===----------------------------------------------------------------------===// +// FFTImagOp +//===----------------------------------------------------------------------===// + +def FFTImagOp : Dsp_Op<"fftImag", [Pure, DeclareOpInterfaceMethods]> { + let summary = "Performs FFT Operation on the input"; + let description = [{ + Takes the input array and returns the imaginary part of the fourier transform, an array of the same size. + }]; + + let arguments = (ins F64Tensor:$lhs); + let results = (outs F64Tensor); + + let builders = [ + OpBuilder<(ins "Value":$lhs)> + ]; +} + //===----------------------------------------------------------------------===// // zeroCrossCountOp //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index f6b8f4f3f4bf..45f9b23213b7 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -576,6 +576,30 @@ void GainOp::inferShapes() { getResult().setType(getLhs().getType()) ;} /// interface. void SubOp::inferShapes() { getResult().setType(getLhs().getType()); } +//===----------------------------------------------------------------------===// +// FFTRealOp +//===----------------------------------------------------------------------===// + +void FFTRealOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs) { + state.addTypes(lhs.getType()); + state.addOperands({lhs}); +} + +void FFTRealOp::inferShapes(){ getResult().setType(getLhs().getType()); } + +//===----------------------------------------------------------------------===// +// FFTImagOp +//===----------------------------------------------------------------------===// + +void FFTImagOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs) { + state.addTypes(lhs.getType()); + state.addOperands({lhs}); +} + +void FFTImagOp::inferShapes(){ getResult().setType(getLhs().getType()); } + //===----------------------------------------------------------------------===// // zeroCrossCountOp //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 5e47ebf8fdd9..491c23565367 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -1716,6 +1716,319 @@ struct LengthOpLowering : public ConversionPattern { } }; +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: FFTRealOp operations +//===----------------------------------------------------------------------===// + +struct FFTRealOpLowering : public ConversionPattern { + // constructor takes the mlir context and the operation as inputs + FFTRealOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::FFTRealOp::getOperationName(), 1, ctx) {} + + // matchandrewrite - actual lowering logic of the operation + LogicalResult // return type is logical --> success or failure + // checks if the correct function is passed and rewrites it + // takes in the pointer to the operation, list of operands, and the rewriter object + // const - function doesn't modify the class it belongs to + // final - can't be overridden + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + // get location of the operation + auto loc = op->getLoc(); + // get the type of the result + auto tensorType = llvm::cast((*op->result_type_begin())); + // convert the tensorType to memrefType + auto memrefType = convertTensorToMemRef(tensorType); + // alloc memory for temp and dealloc when not required + auto alloc_temp_real = insertAllocAndDealloc(memrefType, loc, rewriter); + auto alloc_temp_imag = insertAllocAndDealloc(memrefType, loc, rewriter); + + + // storing the input in real and 0.0 in imag + + // adaptor to get operands + FFTRealOpAdaptor fftRealOpAdaptor(operands); + auto input = fftRealOpAdaptor.getLhs(); + + // bounds of the affine loop + auto lb = rewriter.create(loc, 0); + auto ub = rewriter.create(loc, tensorType.getShape()[0]); + auto step = rewriter.create(loc, 1); + + // load real and imag + auto load_temp = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(load_temp.getBody()); + auto iv = load_temp.getInductionVar(); + auto inputValue = rewriter.create(loc, input, ValueRange{iv}); + auto constantZero = rewriter.create(loc, llvm::APFloat(0.0), rewriter.getF64Type()); + rewriter.create(loc, inputValue, alloc_temp_real, ValueRange{iv}); + rewriter.create(loc, constantZero, alloc_temp_imag, ValueRange{iv}); + rewriter.setInsertionPointAfter(load_temp); + + // alloc memory for reversed and dealloc when not required + auto alloc_reversed_real = insertAllocAndDealloc(memrefType, loc, rewriter); + auto alloc_reversed_imag = insertAllocAndDealloc(memrefType, loc, rewriter); + + // bit reversal constants + auto constant1 = rewriter.create(loc, rewriter.getI64IntegerAttr(1)); + auto constant2 = rewriter.create(loc, rewriter.getI64IntegerAttr(2)); + + // Bit reversal loop + auto bitReversal = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(bitReversal.getBody()); + auto i = bitReversal.getInductionVar(); + // Convert index to i64 + auto i_val = rewriter.create(loc, rewriter.getI64Type(), i); + // Bit reversal logic + auto bit0 = rewriter.create(loc, i_val, constant1); + auto i_val_shr1 = rewriter.create(loc, i_val, constant1); + auto bit1 = rewriter.create(loc, i_val_shr1, constant1); + auto i_val_shr2 = rewriter.create(loc, i_val, constant2); + auto bit2 = rewriter.create(loc, i_val_shr2, constant1); + auto rev_bit0 = rewriter.create(loc, bit0, constant2); + auto rev_bit1 = rewriter.create(loc, bit1, constant1); + auto rev_temp = rewriter.create(loc, rev_bit0, rev_bit1); + auto rev = rewriter.create(loc, rev_temp, bit2); + // Convert back to index + auto reversed_i = rewriter.create(loc, rewriter.getIndexType(), rev); + // Load values from temp arrays + auto real_val = rewriter.create(loc, alloc_temp_real, ValueRange{i}); + auto imag_val = rewriter.create(loc, alloc_temp_imag, ValueRange{i}); + // Store values in reversed arrays + rewriter.create(loc, real_val, alloc_reversed_real, ValueRange{reversed_i}); + rewriter.create(loc, imag_val, alloc_reversed_imag, ValueRange{reversed_i}); + rewriter.setInsertionPointAfter(bitReversal); + + // Cooley-Tukey FFT implementation + auto N = tensorType.getShape()[0]; + auto stages = static_cast(std::log2(N)); + auto stagesValue = rewriter.create(loc, stages); + + // Constants for complex arithmetic + auto pi = rewriter.create(loc, llvm::APFloat(M_PI), rewriter.getF64Type()); + auto neg2 = rewriter.create(loc, llvm::APFloat(-2.0), rewriter.getF64Type()); + + auto fftLoop = rewriter.create(loc, lb, stagesValue, step); + rewriter.setInsertionPointToStart(fftLoop.getBody()); + auto stage = fftLoop.getInductionVar(); + auto half_size = rewriter.create(loc, rewriter.create(loc, 1), stage); + auto full_size = rewriter.create(loc, half_size, rewriter.create(loc, 1)); + + auto outerLoop = rewriter.create(loc, lb, ub, full_size); + rewriter.setInsertionPointToStart(outerLoop.getBody()); + auto start = outerLoop.getInductionVar(); + + auto innerLoop = rewriter.create(loc, lb, half_size, step); + rewriter.setInsertionPointToStart(innerLoop.getBody()); + auto j = innerLoop.getInductionVar(); + + // Calculate indices for even and odd elements + auto even_index = rewriter.create(loc, start, j); + auto odd_index = rewriter.create(loc, even_index, half_size); + + // Calculate twiddle factor + auto j_i64 = rewriter.create(loc, rewriter.getI64Type(), j); + auto j_f64 = rewriter.create(loc, rewriter.getF64Type(), j_i64); + auto full_size_i64 = rewriter.create(loc, rewriter.getI64Type(), full_size); + auto full_size_f64 = rewriter.create(loc, rewriter.getF64Type(), full_size_i64); + auto angle_div = rewriter.create(loc, j_f64, full_size_f64); + auto angle_mul = rewriter.create(loc, neg2, angle_div); + auto angle_final = rewriter.create(loc, pi, angle_mul); + auto cos = rewriter.create(loc, angle_final); + auto sin = rewriter.create(loc, angle_final); + + // Load odd value + auto odd_real = rewriter.create(loc, alloc_reversed_real, ValueRange{odd_index}); + auto odd_imag = rewriter.create(loc, alloc_reversed_imag, ValueRange{odd_index}); + + // Multiply by twiddle factor + auto odd_real_cos = rewriter.create(loc, odd_real, cos); + auto odd_imag_sin = rewriter.create(loc, odd_imag, sin); + auto t_real = rewriter.create(loc, odd_real_cos, odd_imag_sin); + + auto odd_real_sin = rewriter.create(loc, odd_real, sin); + auto odd_imag_cos = rewriter.create(loc, odd_imag, cos); + auto t_imag = rewriter.create(loc, odd_real_sin, odd_imag_cos); + + // Load even value + auto even_real = rewriter.create(loc, alloc_reversed_real, ValueRange{even_index}); + auto even_imag = rewriter.create(loc, alloc_reversed_imag, ValueRange{even_index}); + + // Butterfly operation + auto new_even_real = rewriter.create(loc, even_real, t_real); + auto new_even_imag = rewriter.create(loc, even_imag, t_imag); + auto new_odd_real = rewriter.create(loc, even_real, t_real); + auto new_odd_imag = rewriter.create(loc, even_imag, t_imag); + + // Store results + rewriter.create(loc, new_even_real, alloc_reversed_real, ValueRange{even_index}); + rewriter.create(loc, new_even_imag, alloc_reversed_imag, ValueRange{even_index}); + rewriter.create(loc, new_odd_real, alloc_reversed_real, ValueRange{odd_index}); + rewriter.create(loc, new_odd_imag, alloc_reversed_imag, ValueRange{odd_index}); + + // replace the operation with the final value + rewriter.replaceOp(op, alloc_reversed_real); + return success(); + } +}; + + +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: FFTImagOp operations +//===----------------------------------------------------------------------===// + +struct FFTImagOpLowering : public ConversionPattern { + // constructor takes the mlir context and the operation as inputs + FFTImagOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::FFTImagOp::getOperationName(), 1, ctx) {} + + // matchandrewrite - actual lowering logic of the operation + LogicalResult // return type is logical --> success or failure + // checks if the correct function is passed and rewrites it + // takes in the pointer to the operation, list of operands, and the rewriter object + // const - function doesn't modify the class it belongs to + // final - can't be overridden + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + // get location of the operation + auto loc = op->getLoc(); + // get the type of the result + auto tensorType = llvm::cast((*op->result_type_begin())); + // convert the tensorType to memrefType + auto memrefType = convertTensorToMemRef(tensorType); + // alloc memory for temp and dealloc when not required + auto alloc_temp_real = insertAllocAndDealloc(memrefType, loc, rewriter); + auto alloc_temp_imag = insertAllocAndDealloc(memrefType, loc, rewriter); + + + // storing the input in real and 0.0 in imag + + // adaptor to get operands + FFTImagOpAdaptor fftImagOpAdaptor(operands); + auto input = fftImagOpAdaptor.getLhs(); + + // bounds of the affine loop + auto lb = rewriter.create(loc, 0); + auto ub = rewriter.create(loc, tensorType.getShape()[0]); + auto step = rewriter.create(loc, 1); + + // load real and imag + auto load_temp = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(load_temp.getBody()); + auto iv = load_temp.getInductionVar(); + auto inputValue = rewriter.create(loc, input, ValueRange{iv}); + auto constantZero = rewriter.create(loc, llvm::APFloat(0.0), rewriter.getF64Type()); + rewriter.create(loc, inputValue, alloc_temp_real, ValueRange{iv}); + rewriter.create(loc, constantZero, alloc_temp_imag, ValueRange{iv}); + rewriter.setInsertionPointAfter(load_temp); + + // alloc memory for reversed and dealloc when not required + auto alloc_reversed_real = insertAllocAndDealloc(memrefType, loc, rewriter); + auto alloc_reversed_imag = insertAllocAndDealloc(memrefType, loc, rewriter); + + // bit reversal constants + auto constant1 = rewriter.create(loc, rewriter.getI64IntegerAttr(1)); + auto constant2 = rewriter.create(loc, rewriter.getI64IntegerAttr(2)); + + // Bit reversal loop + auto bitReversal = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(bitReversal.getBody()); + auto i = bitReversal.getInductionVar(); + // Convert index to i64 + auto i_val = rewriter.create(loc, rewriter.getI64Type(), i); + // Bit reversal logic + auto bit0 = rewriter.create(loc, i_val, constant1); + auto i_val_shr1 = rewriter.create(loc, i_val, constant1); + auto bit1 = rewriter.create(loc, i_val_shr1, constant1); + auto i_val_shr2 = rewriter.create(loc, i_val, constant2); + auto bit2 = rewriter.create(loc, i_val_shr2, constant1); + auto rev_bit0 = rewriter.create(loc, bit0, constant2); + auto rev_bit1 = rewriter.create(loc, bit1, constant1); + auto rev_temp = rewriter.create(loc, rev_bit0, rev_bit1); + auto rev = rewriter.create(loc, rev_temp, bit2); + // Convert back to index + auto reversed_i = rewriter.create(loc, rewriter.getIndexType(), rev); + // Load values from temp arrays + auto real_val = rewriter.create(loc, alloc_temp_real, ValueRange{i}); + auto imag_val = rewriter.create(loc, alloc_temp_imag, ValueRange{i}); + // Store values in reversed arrays + rewriter.create(loc, real_val, alloc_reversed_real, ValueRange{reversed_i}); + rewriter.create(loc, imag_val, alloc_reversed_imag, ValueRange{reversed_i}); + rewriter.setInsertionPointAfter(bitReversal); + + // Cooley-Tukey FFT implementation + auto N = tensorType.getShape()[0]; + auto stages = static_cast(std::log2(N)); + auto stagesValue = rewriter.create(loc, stages); + + // Constants for complex arithmetic + auto pi = rewriter.create(loc, llvm::APFloat(M_PI), rewriter.getF64Type()); + auto neg2 = rewriter.create(loc, llvm::APFloat(-2.0), rewriter.getF64Type()); + + auto fftLoop = rewriter.create(loc, lb, stagesValue, step); + rewriter.setInsertionPointToStart(fftLoop.getBody()); + auto stage = fftLoop.getInductionVar(); + auto half_size = rewriter.create(loc, rewriter.create(loc, 1), stage); + auto full_size = rewriter.create(loc, half_size, rewriter.create(loc, 1)); + + auto outerLoop = rewriter.create(loc, lb, ub, full_size); + rewriter.setInsertionPointToStart(outerLoop.getBody()); + auto start = outerLoop.getInductionVar(); + + auto innerLoop = rewriter.create(loc, lb, half_size, step); + rewriter.setInsertionPointToStart(innerLoop.getBody()); + auto j = innerLoop.getInductionVar(); + + // Calculate indices for even and odd elements + auto even_index = rewriter.create(loc, start, j); + auto odd_index = rewriter.create(loc, even_index, half_size); + + // Calculate twiddle factor + auto j_i64 = rewriter.create(loc, rewriter.getI64Type(), j); + auto j_f64 = rewriter.create(loc, rewriter.getF64Type(), j_i64); + auto full_size_i64 = rewriter.create(loc, rewriter.getI64Type(), full_size); + auto full_size_f64 = rewriter.create(loc, rewriter.getF64Type(), full_size_i64); + auto angle_div = rewriter.create(loc, j_f64, full_size_f64); + auto angle_mul = rewriter.create(loc, neg2, angle_div); + auto angle_final = rewriter.create(loc, pi, angle_mul); + auto cos = rewriter.create(loc, angle_final); + auto sin = rewriter.create(loc, angle_final); + + // Load odd value + auto odd_real = rewriter.create(loc, alloc_reversed_real, ValueRange{odd_index}); + auto odd_imag = rewriter.create(loc, alloc_reversed_imag, ValueRange{odd_index}); + + // Multiply by twiddle factor + auto odd_real_cos = rewriter.create(loc, odd_real, cos); + auto odd_imag_sin = rewriter.create(loc, odd_imag, sin); + auto t_real = rewriter.create(loc, odd_real_cos, odd_imag_sin); + + auto odd_real_sin = rewriter.create(loc, odd_real, sin); + auto odd_imag_cos = rewriter.create(loc, odd_imag, cos); + auto t_imag = rewriter.create(loc, odd_real_sin, odd_imag_cos); + + // Load even value + auto even_real = rewriter.create(loc, alloc_reversed_real, ValueRange{even_index}); + auto even_imag = rewriter.create(loc, alloc_reversed_imag, ValueRange{even_index}); + + // Butterfly operation + auto new_even_real = rewriter.create(loc, even_real, t_real); + auto new_even_imag = rewriter.create(loc, even_imag, t_imag); + auto new_odd_real = rewriter.create(loc, even_real, t_real); + auto new_odd_imag = rewriter.create(loc, even_imag, t_imag); + + // Store results + rewriter.create(loc, new_even_real, alloc_reversed_real, ValueRange{even_index}); + rewriter.create(loc, new_even_imag, alloc_reversed_imag, ValueRange{even_index}); + rewriter.create(loc, new_odd_real, alloc_reversed_real, ValueRange{odd_index}); + rewriter.create(loc, new_odd_imag, alloc_reversed_imag, ValueRange{odd_index}); + + // replace the operation with the final value + rewriter.replaceOp(op, alloc_reversed_imag); + return success(); + } +}; + //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: FIRFilterResSymmOptimizedOp operations //===----------------------------------------------------------------------===// @@ -6287,7 +6600,7 @@ void ToyToAffineLoweringPass::runOnOperation() { RunLenEncodingOpLowering, FIRFilterResSymmOptimizedOpLowering, LengthOpLowering, ReverseInputOpLowering, PaddingOpLowering, FIRFilterYSymmOptimizedOpLowering , FFT1DRealSymmOpLowering, - FFT1DImgConjSymmOpLowering >( + FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering >( &getContext()); // With the target and rewrite patterns defined, we can now attempt the diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index 6021b3b75a6e..f62b2d13c0a4 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -373,11 +373,29 @@ class MLIRGenImpl { // Modulo Op if(callee == "modulo"){ if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.sub " + emitError(location, "MLIR codegen encountered an error: dsp.modulo " "accepts only 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1]); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); + } + + if (callee == "fftReal") { + if (call.getArgs().size() != 1) { + emitError(location, "MLIR codegen encountered an error: dsp.zeroCrossCount " + "accepts only 1 arguments"); + return nullptr; + } + return builder.create(location, operands[0]); + } + + if (callee == "fftImag") { + if (call.getArgs().size() != 1) { + emitError(location, "MLIR codegen encountered an error: dsp.zeroCrossCount " + "accepts only 1 arguments"); + return nullptr; + } + return builder.create(location, operands[0]); } if(callee == "zeroCrossCount"){ diff --git a/mlir/test/Examples/DspExample/dsp_fft.py b/mlir/test/Examples/DspExample/dsp_fft.py new file mode 100644 index 000000000000..e3d8b8f9b949 --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_fft.py @@ -0,0 +1,7 @@ +def main() { + var a = [0.0, 10.0, 340.0, 30.0, 40.0, 110.0, 60.0, 250.0]; + var b = fftReal(a); + var c = fftImag(a); + print(b); + print(c); +} diff --git a/mlir/test/Examples/DspExample/fft.mlir b/mlir/test/Examples/DspExample/fft.mlir new file mode 100644 index 000000000000..67172b3175fe --- /dev/null +++ b/mlir/test/Examples/DspExample/fft.mlir @@ -0,0 +1,189 @@ +module { + func.func @main() { + %alloc = memref.alloc() : memref<8xf64> + %alloc_0 = memref.alloc() : memref<8xf64> + %alloc_1 = memref.alloc() : memref<8xf64> + %alloc_2 = memref.alloc() : memref<8xf64> + %alloc_3 = memref.alloc() : memref<8xf64> + %alloc_4 = memref.alloc() : memref<8xf64> + %alloc_5 = memref.alloc() : memref<8xf64> + %alloc_6 = memref.alloc() : memref<8xf64> + %alloc_7 = memref.alloc() : memref<8xf64> + %c0 = arith.constant 0 : index + %c1 = arith.constant 1 : index + %c2 = arith.constant 2 : index + %c3 = arith.constant 3 : index + %c4 = arith.constant 4 : index + %c5 = arith.constant 5 : index + %c6 = arith.constant 6 : index + %c7 = arith.constant 7 : index + %cst = arith.constant 0.000000e+00 : f64 + affine.store %cst, %alloc_7[%c0] : memref<8xf64> + %cst_8 = arith.constant 1.000000e+01 : f64 + affine.store %cst_8, %alloc_7[%c1] : memref<8xf64> + %cst_9 = arith.constant 3.400000e+02 : f64 + affine.store %cst_9, %alloc_7[%c2] : memref<8xf64> + %cst_10 = arith.constant 3.000000e+01 : f64 + affine.store %cst_10, %alloc_7[%c3] : memref<8xf64> + %cst_11 = arith.constant 4.000000e+01 : f64 + affine.store %cst_11, %alloc_7[%c4] : memref<8xf64> + %cst_12 = arith.constant 1.100000e+02 : f64 + affine.store %cst_12, %alloc_7[%c5] : memref<8xf64> + %cst_13 = arith.constant 6.000000e+01 : f64 + affine.store %cst_13, %alloc_7[%c6] : memref<8xf64> + %cst_14 = arith.constant 2.500000e+02 : f64 + affine.store %cst_14, %alloc_7[%c7] : memref<8xf64> + %c0_15 = arith.constant 0 : index + %c8 = arith.constant 8 : index + %c1_16 = arith.constant 1 : index + scf.for %arg0 = %c0_15 to %c8 step %c1_16 { + %0 = memref.load %alloc_7[%arg0] : memref<8xf64> + %cst_28 = arith.constant 0.000000e+00 : f64 + memref.store %0, %alloc_6[%arg0] : memref<8xf64> + memref.store %cst_28, %alloc_5[%arg0] : memref<8xf64> + } + %c1_i64 = arith.constant 1 : i64 + %c2_i64 = arith.constant 2 : i64 + scf.for %arg0 = %c0_15 to %c8 step %c1_16 { + %0 = arith.index_cast %arg0 : index to i64 + %1 = arith.andi %0, %c1_i64 : i64 + %2 = arith.shrui %0, %c1_i64 : i64 + %3 = arith.andi %2, %c1_i64 : i64 + %4 = arith.shrui %0, %c2_i64 : i64 + %5 = arith.andi %4, %c1_i64 : i64 + %6 = arith.shli %1, %c2_i64 : i64 + %7 = arith.shli %3, %c1_i64 : i64 + %8 = arith.ori %6, %7 : i64 + %9 = arith.ori %8, %5 : i64 + %10 = arith.index_cast %9 : i64 to index + %11 = memref.load %alloc_6[%arg0] : memref<8xf64> + %12 = memref.load %alloc_5[%arg0] : memref<8xf64> + memref.store %11, %alloc_4[%10] : memref<8xf64> + memref.store %12, %alloc_3[%10] : memref<8xf64> + } + %c3_17 = arith.constant 3 : index + %cst_18 = arith.constant 3.1415926535897931 : f64 + %cst_19 = arith.constant -2.000000e+00 : f64 + scf.for %arg0 = %c0_15 to %c3_17 step %c1_16 { + %c1_28 = arith.constant 1 : index + %0 = arith.shli %c1_28, %arg0 : index + %c1_29 = arith.constant 1 : index + %1 = arith.shli %0, %c1_29 : index + scf.for %arg1 = %c0_15 to %c8 step %1 { + scf.for %arg2 = %c0_15 to %0 step %c1_16 { + %2 = arith.addi %arg1, %arg2 : index + %3 = arith.addi %2, %0 : index + %4 = arith.index_cast %arg2 : index to i64 + %5 = arith.sitofp %4 : i64 to f64 + %6 = arith.index_cast %1 : index to i64 + %7 = arith.sitofp %6 : i64 to f64 + %8 = arith.divf %5, %7 : f64 + %9 = arith.mulf %cst_19, %8 : f64 + %10 = arith.mulf %cst_18, %9 : f64 + %11 = math.cos %10 : f64 + %12 = math.sin %10 : f64 + %13 = memref.load %alloc_4[%3] : memref<8xf64> + %14 = memref.load %alloc_3[%3] : memref<8xf64> + %15 = arith.mulf %13, %11 : f64 + %16 = arith.mulf %14, %12 : f64 + %17 = arith.subf %15, %16 : f64 + %18 = arith.mulf %13, %12 : f64 + %19 = arith.mulf %14, %11 : f64 + %20 = arith.addf %18, %19 : f64 + %21 = memref.load %alloc_4[%2] : memref<8xf64> + %22 = memref.load %alloc_3[%2] : memref<8xf64> + %23 = arith.addf %21, %17 : f64 + %24 = arith.addf %22, %20 : f64 + %25 = arith.subf %21, %17 : f64 + %26 = arith.subf %22, %20 : f64 + memref.store %23, %alloc_4[%2] : memref<8xf64> + memref.store %24, %alloc_3[%2] : memref<8xf64> + memref.store %25, %alloc_4[%3] : memref<8xf64> + memref.store %26, %alloc_3[%3] : memref<8xf64> + } + } + } + %c0_20 = arith.constant 0 : index + %c8_21 = arith.constant 8 : index + %c1_22 = arith.constant 1 : index + scf.for %arg0 = %c0_20 to %c8_21 step %c1_22 { + %0 = memref.load %alloc_7[%arg0] : memref<8xf64> + %cst_28 = arith.constant 0.000000e+00 : f64 + memref.store %0, %alloc_2[%arg0] : memref<8xf64> + memref.store %cst_28, %alloc_1[%arg0] : memref<8xf64> + } + %c1_i64_23 = arith.constant 1 : i64 + %c2_i64_24 = arith.constant 2 : i64 + scf.for %arg0 = %c0_20 to %c8_21 step %c1_22 { + %0 = arith.index_cast %arg0 : index to i64 + %1 = arith.andi %0, %c1_i64_23 : i64 + %2 = arith.shrui %0, %c1_i64_23 : i64 + %3 = arith.andi %2, %c1_i64_23 : i64 + %4 = arith.shrui %0, %c2_i64_24 : i64 + %5 = arith.andi %4, %c1_i64_23 : i64 + %6 = arith.shli %1, %c2_i64_24 : i64 + %7 = arith.shli %3, %c1_i64_23 : i64 + %8 = arith.ori %6, %7 : i64 + %9 = arith.ori %8, %5 : i64 + %10 = arith.index_cast %9 : i64 to index + %11 = memref.load %alloc_2[%arg0] : memref<8xf64> + %12 = memref.load %alloc_1[%arg0] : memref<8xf64> + memref.store %11, %alloc_0[%10] : memref<8xf64> + memref.store %12, %alloc[%10] : memref<8xf64> + } + %c3_25 = arith.constant 3 : index + %cst_26 = arith.constant 3.1415926535897931 : f64 + %cst_27 = arith.constant -2.000000e+00 : f64 + scf.for %arg0 = %c0_20 to %c3_25 step %c1_22 { + %c1_28 = arith.constant 1 : index + %0 = arith.shli %c1_28, %arg0 : index + %c1_29 = arith.constant 1 : index + %1 = arith.shli %0, %c1_29 : index + scf.for %arg1 = %c0_20 to %c8_21 step %1 { + scf.for %arg2 = %c0_20 to %0 step %c1_22 { + %2 = arith.addi %arg1, %arg2 : index + %3 = arith.addi %2, %0 : index + %4 = arith.index_cast %arg2 : index to i64 + %5 = arith.sitofp %4 : i64 to f64 + %6 = arith.index_cast %1 : index to i64 + %7 = arith.sitofp %6 : i64 to f64 + %8 = arith.divf %5, %7 : f64 + %9 = arith.mulf %cst_27, %8 : f64 + %10 = arith.mulf %cst_26, %9 : f64 + %11 = math.cos %10 : f64 + %12 = math.sin %10 : f64 + %13 = memref.load %alloc_0[%3] : memref<8xf64> + %14 = memref.load %alloc[%3] : memref<8xf64> + %15 = arith.mulf %13, %11 : f64 + %16 = arith.mulf %14, %12 : f64 + %17 = arith.subf %15, %16 : f64 + %18 = arith.mulf %13, %12 : f64 + %19 = arith.mulf %14, %11 : f64 + %20 = arith.addf %18, %19 : f64 + %21 = memref.load %alloc_0[%2] : memref<8xf64> + %22 = memref.load %alloc[%2] : memref<8xf64> + %23 = arith.addf %21, %17 : f64 + %24 = arith.addf %22, %20 : f64 + %25 = arith.subf %21, %17 : f64 + %26 = arith.subf %22, %20 : f64 + memref.store %23, %alloc_0[%2] : memref<8xf64> + memref.store %24, %alloc[%2] : memref<8xf64> + memref.store %25, %alloc_0[%3] : memref<8xf64> + memref.store %26, %alloc[%3] : memref<8xf64> + } + } + } + dsp.print %alloc_4 : memref<8xf64> + dsp.print %alloc : memref<8xf64> + memref.dealloc %alloc_7 : memref<8xf64> + memref.dealloc %alloc_6 : memref<8xf64> + memref.dealloc %alloc_5 : memref<8xf64> + memref.dealloc %alloc_4 : memref<8xf64> + memref.dealloc %alloc_3 : memref<8xf64> + memref.dealloc %alloc_2 : memref<8xf64> + memref.dealloc %alloc_1 : memref<8xf64> + memref.dealloc %alloc_0 : memref<8xf64> + memref.dealloc %alloc : memref<8xf64> + return + } +} From 67ed5688a989bd6b2cd76f30151ce040cd10f153 Mon Sep 17 00:00:00 2001 From: AtharvaKhedkar Date: Wed, 16 Oct 2024 11:20:24 -0700 Subject: [PATCH 05/45] add echo,hearing_aid and vibrationanalysis apps --- .../Results/TryResultScript/hearing_aid.py | 32 +++++++++++++++++++ .../Output/TryDSPApps/echocancelling.py | 26 +++++++++++++++ .../Output/TryDSPApps/vibrationAnalysis.py | 32 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/hearing_aid.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/echocancelling.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/vibrationAnalysis.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/hearing_aid.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/hearing_aid.py new file mode 100644 index 000000000000..744c023a45f0 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/hearing_aid.py @@ -0,0 +1,32 @@ +def main() { +var fs = 8000; + # var step = 1/8000; + # print(step); + var input = getRangeOfVector(0, 20000000, 0.000125); + var f_sig = 500; + var pi = 3.14159265359; + var getMultiplier = 2 * pi * f_sig; + # print(getMultiplier); + var getSinDuration = gain(input, getMultiplier); + # print(getSinDuration); + var clean_sig = sin(getSinDuration ); + + #define a noise signal with freq = 3000 + var f_noise = 3000; + var getNoiseSinDuration = gain(input, 2 * pi * f_noise); + var noise = sin(getNoiseSinDuration); + var noise1 = gain(noise, 0.5); + + var noisy_sig = clean_sig + noise1; + # print(noisy_sig); + # print(clean_sig); + var mu = 0.01; + var filterSize = 32; + var y = lmsFilterResponse(noisy_sig, clean_sig, mu, filterSize); + var G1 = 1002300; + var sol = gain(y,G1); + # print(y); + print(sol); + +} + diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/echocancelling.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/echocancelling.py new file mode 100644 index 000000000000..3d45361b848b --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/echocancelling.py @@ -0,0 +1,26 @@ +def main() { + var fs = 8000; + # var step = 1/8000; + # print(step); + var t = getRangeOfVector(0,100, 0.000125); + var f_sig = 500; + var pi = 3.14159265359; + var getMultiplier = 2 * pi * f_sig; + # print(getMultiplier); + var getSinDuration = gain(t, getMultiplier); + # print(getSinDuration); + var clean_sig = sin(getSinDuration ); + + #define a noise signal with freq = 3000 + var noise = delay(clean_sig, 2); + # var noise1 = gain(noise, 0.5); + + var noisy_sig = clean_sig + noise; + # print(noisy_sig); + # print(clean_sig); + var mu = 0.01; + var filterSize = 32; + var y = lmsFilterResponse(noisy_sig, clean_sig, mu, filterSize); + print(y); + +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/vibrationAnalysis.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/vibrationAnalysis.py new file mode 100644 index 000000000000..1fb6defd7e3d --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/vibrationAnalysis.py @@ -0,0 +1,32 @@ +def main() { + var fs = 1000; + # var step = 1/fs; + # print(step); + var t = getRangeOfVector(0,fs,0.001); + var pi = 3.14159265359; + var getMultiplier = 2 * pi * 50; + # print(getMultiplier); + var getSinDuration = gain(t, getMultiplier); + var sig1 = sin(getSinDuration ); + var getMultiplier2 = 2 * pi * 120; + var getSinDuration2 = gain(t, getMultiplier2); + var sinsig2 = sin(getSinDuration2); + var sig2 = gain(sinsig2, 0.5); + var signal = sig1 + sig2; + var noise = delay(signal, 5); + var noisy_sig = signal + noise; + var threshold = 4; + + var fft_real = fft1dreal(noisy_sig); + var fft_img = fft1dimg(noisy_sig); + + var sq_abs = square(fft_real) + square(fft_img) ; + # sum = sum(sq_abs) + var sum1 = sum(sq_abs); + # res = gain(sum , 1/N) + var len1 = len(t); + var res = sum1 / len1; + # print(sq_abs); + var GetThresholdReal = threshold( sq_abs , threshold); + print(GetThresholdReal); +} \ No newline at end of file From 6e82ddd8af28aca13b727cc042a90a0a3b99ba28 Mon Sep 17 00:00:00 2001 From: kurtis-b <22698418+kurtis-b@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:01:23 -0700 Subject: [PATCH 06/45] Zero Cross Count op (#8) * Bitwiseandop and zeroCrossCountOp --------- Co-authored-by: Curt John Bansil Co-authored-by: AtharvaKhedkar --- .../dsp/SimpleBlocks/include/toy/Ops.td | 28 +- .../dsp/SimpleBlocks/mlir/Dialect.cpp | 1528 +-- .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 8206 +++++++++-------- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 456 +- .../Examples/DspExample/dsp_bitwiseand_op.py | 20 + .../DspExample/zeroCross/zeroCross.mlir | 46 + .../DspExample/zeroCross/zeroCross10.py | 27 +- 7 files changed, 5512 insertions(+), 4799 deletions(-) create mode 100644 mlir/test/Examples/DspExample/dsp_bitwiseand_op.py create mode 100644 mlir/test/Examples/DspExample/zeroCross/zeroCross.mlir diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 1af513274e4c..09c284261c6b 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -306,6 +306,30 @@ def DivOp : Dsp_Op<"div", ]; } +//===----------------------------------------------------------------------===// +// BitwiseAndOp +//===----------------------------------------------------------------------===// + +def BitwiseAndOp : Dsp_Op<"bitwiseand", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "bit-wise and operation"; + let description = [{ + The "bitwiseand" operation performs bit-wise and between two + tensors. The shapes of the tensor operands are expected to match. + }]; + + let arguments = (ins F64Tensor:$lhs, F64Tensor:$rhs); + let results = (outs F64Tensor); + + // Indicate that the operation has a custom parser and printer method. + let hasCustomAssemblyFormat = 1; + + // Allow building a BitwiseAndOp with from the two input operands. + let builders = [ + OpBuilder<(ins "Value":$lhs, "Value":$rhs)> + ]; +} + //===----------------------------------------------------------------------===// // PrintOp //===----------------------------------------------------------------------===// @@ -577,9 +601,9 @@ def FFTImagOp : Dsp_Op<"fftImag", [Pure, DeclareOpInterfaceMethods]> { - let summary = "shifting tensor by given number"; + let summary = "count the crosses through zero"; let description = [{ - The "zeroCrossCountOp" operation detects no of zero crosses in a given array -- + The "zeroCrossCount" operation detects no of zero crosses in a given array -- ex: [-1 , -2 , 3, 0 , 0, -2] has 2 zero-crosses }]; diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index 45f9b23213b7..5c0ea044a93e 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -10,9 +10,9 @@ // operation verification. // //===----------------------------------------------------------------------===// -#include #include "toy/Dialect.h" #include "toy/DebugConfig.h" +#include #include "mlir/IR/Attributes.h" #include "mlir/IR/Builders.h" @@ -216,7 +216,8 @@ void ConstantOp::print(mlir::OpAsmPrinter &printer) { mlir::LogicalResult ConstantOp::verify() { // If the return type of the constant is not an unranked tensor, the shape // must match the shape of the attribute holding the data. - auto resultType = llvm::dyn_cast(getResult().getType()); + auto resultType = + llvm::dyn_cast(getResult().getType()); if (!resultType) return success(); @@ -410,6 +411,27 @@ void DivOp::print(mlir::OpAsmPrinter &p) { printBinaryOp(p, *this); } /// interface. void DivOp::inferShapes() { getResult().setType(getLhs().getType()); } +//===----------------------------------------------------------------------===// +// BitwiseAndOp +//===----------------------------------------------------------------------===// + +void BitwiseAndOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs, mlir::Value rhs) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({lhs, rhs}); +} + +mlir::ParseResult BitwiseAndOp::parse(mlir::OpAsmParser &parser, + mlir::OperationState &result) { + return parseBinaryOp(parser, result); +} + +void BitwiseAndOp::print(mlir::OpAsmPrinter &p) { printBinaryOp(p, *this); } + +/// Infer the output shape of the BitwiseAndOp, this is required by the shape +/// inference interface. +void BitwiseAndOp::inferShapes() { getResult().setType(getLhs().getType()); } + //===----------------------------------------------------------------------===// // ReturnOp //===----------------------------------------------------------------------===// @@ -438,7 +460,8 @@ mlir::LogicalResult ReturnOp::verify() { auto resultType = results.front(); // Check that the result type of the function matches the operand type. - if (inputType == resultType || llvm::isa(inputType) || + if (inputType == resultType || + llvm::isa(inputType) || llvm::isa(resultType)) return mlir::success(); @@ -478,40 +501,40 @@ mlir::LogicalResult TransposeOp::verify() { return mlir::success(); } - //===----------------------------------------------------------------------===// // DelayOp //===----------------------------------------------------------------------===// // void DelayOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, // mlir::Value lhs, unsigned rhs){ void DelayOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs){ - // - // state.addTypes(UnrankedTensorType::get(builder.getF64Type()), builder.getI32Type()); - state.addTypes(UnrankedTensorType::get(builder.getF64Type())); //working - state.addOperands({lhs, rhs}); - // state.addOperands(value); - - } - - mlir::LogicalResult DelayOp::verify(){ - // auto inputType1 = llvm::dyn_cast(getOperand(0).getType()); - // auto inputType2 = llvm::dyn_cast(getOperand(1).getType()); - // auto resultType = llvm::dyn_cast(getType()); - // if(!inputType || !resultType) - // return mlir::success(); + mlir::Value lhs, mlir::Value rhs) { + // + // state.addTypes(UnrankedTensorType::get(builder.getF64Type()), + // builder.getI32Type()); + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); // working + state.addOperands({lhs, rhs}); + // state.addOperands(value); +} - return mlir::success(); - } +mlir::LogicalResult DelayOp::verify() { + // auto inputType1 = + // llvm::dyn_cast(getOperand(0).getType()); auto inputType2 + // = llvm::dyn_cast(getOperand(1).getType()); auto + // resultType = llvm::dyn_cast(getType()); if(!inputType || + // !resultType) + // return mlir::success(); + + return mlir::success(); +} // void DelayOp::inferShapes() { getResult().setType(getOperand(0).getType()) ;} -//getLHS defined with Operation as : -// fro addOp +// getLHS defined with Operation as : +// fro addOp // ::mlir::TypedValue<::mlir::TensorType> AddOp::getLhs() { -// return ::llvm::cast<::mlir::TypedValue<::mlir::TensorType>>(*getODSOperands(0).begin()); +// return +// ::llvm::cast<::mlir::TypedValue<::mlir::TensorType>>(*getODSOperands(0).begin()); // } -void DelayOp::inferShapes() { getResult().setType(getLhs().getType()) ;} - +void DelayOp::inferShapes() { getResult().setType(getLhs().getType()); } //===----------------------------------------------------------------------===// // GainOp @@ -519,27 +542,31 @@ void DelayOp::inferShapes() { getResult().setType(getLhs().getType()) ;} // void GainOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, // mlir::Value lhs, unsigned rhs){ // void GainOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, -// mlir::Value lhs, mlir::Float64Type rhs){ +// mlir::Value lhs, mlir::Float64Type rhs){ void GainOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs){ - // state.addTypes(UnrankedTensorType::get(builder.getF64Type()), builder.getI32Type()); - // state.addTypes(UnrankedTensorType::get(builder.getF64Type())); - // state.addTypes({UnrankedTensorType::get(builder.getF64Type()), builder.getF64Type()}); //working - state.addTypes(UnrankedTensorType::get(builder.getF64Type())); - state.addOperands({lhs, rhs}); - // state.addOperands({rhs}); - // state.addTypes(); - // state.addAttribute("rhs", rhs); - // state.addAttribute("rhs", builder.getF64FloatAttr(builder.getF64Type())); - // state.addAttribute("rhs", builder.getF64Type()); - // state.addAttribute("rhs", builder.getFloatAttr(builder.getF64Type() , rhs)); - // state.addOperands(value); - } + mlir::Value lhs, mlir::Value rhs) { + // state.addTypes(UnrankedTensorType::get(builder.getF64Type()), + // builder.getI32Type()); + // state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + // state.addTypes({UnrankedTensorType::get(builder.getF64Type()), + // builder.getF64Type()}); //working + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({lhs, rhs}); + // state.addOperands({rhs}); + // state.addTypes(); + // state.addAttribute("rhs", rhs); + // state.addAttribute("rhs", builder.getF64FloatAttr(builder.getF64Type())); + // state.addAttribute("rhs", builder.getF64Type()); + // state.addAttribute("rhs", builder.getFloatAttr(builder.getF64Type() , + // rhs)); state.addOperands(value); +} // mlir::LogicalResult GainOp::verify(){ -// auto inputType1 = llvm::dyn_cast(getOperand(0).getType()); -// auto inputType2 = llvm::dyn_cast(getOperand(1).getType()); -// // auto inputType2 = llvm::dyn_cast(getOperand(1).getType()); +// auto inputType1 = +// llvm::dyn_cast(getOperand(0).getType()); auto +// inputType2 = llvm::dyn_cast(getOperand(1).getType()); +// // auto inputType2 = +// llvm::dyn_cast(getOperand(1).getType()); // // auto resultType = llvm::dyn_cast(getType()); // // if(!inputType || !resultType) // // return mlir::success(); @@ -548,33 +575,34 @@ void GainOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, // } // void GainOp::inferShapes() { getResult().setType(getOperand(0).getType()) ;} -//getLHS defined with Operation as : -// fro addOp +// getLHS defined with Operation as : +// fro addOp // ::mlir::TypedValue<::mlir::TensorType> AddOp::getLhs() { -// return ::llvm::cast<::mlir::TypedValue<::mlir::TensorType>>(*getODSOperands(0).begin()); +// return +// ::llvm::cast<::mlir::TypedValue<::mlir::TensorType>>(*getODSOperands(0).begin()); // } -void GainOp::inferShapes() { getResult().setType(getLhs().getType()) ;} +void GainOp::inferShapes() { getResult().setType(getLhs().getType()); } //===----------------------------------------------------------------------===// - // SubOp - //===----------------------------------------------------------------------===// +// SubOp +//===----------------------------------------------------------------------===// - void SubOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs) { - state.addTypes(UnrankedTensorType::get(builder.getF64Type())); - state.addOperands({lhs, rhs}); - } +void SubOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs, mlir::Value rhs) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({lhs, rhs}); +} - // mlir::ParseResult SubOp::parse(mlir::OpAsmParser &parser, - // mlir::OperationState &result) { - // return parseBinaryOp(parser, result); - // } +// mlir::ParseResult SubOp::parse(mlir::OpAsmParser &parser, +// mlir::OperationState &result) { +// return parseBinaryOp(parser, result); +// } - // void SubOp::print(mlir::OpAsmPrinter &p) { printBinaryOp(p, *this); } +// void SubOp::print(mlir::OpAsmPrinter &p) { printBinaryOp(p, *this); } - /// Infer the output shape of the SubOp, this is required by the shape inference - /// interface. - void SubOp::inferShapes() { getResult().setType(getLhs().getType()); } +/// Infer the output shape of the SubOp, this is required by the shape inference +/// interface. +void SubOp::inferShapes() { getResult().setType(getLhs().getType()); } //===----------------------------------------------------------------------===// // FFTRealOp @@ -612,52 +640,53 @@ void zeroCrossCountOp::build(mlir::OpBuilder &builder, mlir::OperationState &sta state.addOperands({lhs}); } -/// Infer the output shape of the zeroCrossCountOp, this is required by the shape inference - /// interface. - void zeroCrossCountOp::inferShapes() { getResult().setType(getLhs().getType()); } - +/// Infer the output shape of the zeroCrossCountOp, this is required by the +/// shape inference interface. +void zeroCrossCountOp::inferShapes() { + getResult().setType(getLhs().getType()); +} //===----------------------------------------------------------------------===// // FIRFilterResponseOp //===----------------------------------------------------------------------===// -void FIRFilterResponseOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs) { +void FIRFilterResponseOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value lhs, + mlir::Value rhs) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands({lhs, rhs}); } - - -/// Infer the output shape of the FIRFilterResponseOp, this is required by the shape inference -/// interface. -//ToDo -- shape should be the length of Lhs + Rhs - 1 -void FIRFilterResponseOp::inferShapes() { - //get the shape of Lhs & rhs - //add the shape for each dimension - // auto tensorInput = llvm::cast(getLhs().getType()); - auto tensorInput = getLhs().getType(); +/// Infer the output shape of the FIRFilterResponseOp, this is required by the +/// shape inference interface. +// ToDo -- shape should be the length of Lhs + Rhs - 1 +void FIRFilterResponseOp::inferShapes() { + // get the shape of Lhs & rhs + // add the shape for each dimension + // auto tensorInput = llvm::cast(getLhs().getType()); + auto tensorInput = getLhs().getType(); auto shapeOfInput = tensorInput.getShape(); auto tensorFilter = getRhs().getType(); auto shapeOfFilter = tensorFilter.getShape(); - std::vector shapeForOutput ; + std::vector shapeForOutput; - for(size_t i=0; i < shapeOfInput.size() ; i++){ + for (size_t i = 0; i < shapeOfInput.size(); i++) { shapeForOutput.push_back(shapeOfInput[i] + shapeOfFilter[i] - 1); } - - mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - getLhs().getType().getElementType()); - // getResult().setType(getLhs().getType()); + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getLhs().getType().getElementType()); + + // getResult().setType(getLhs().getType()); getResult().setType(manipulatedType); - } +} -//get rank of Input & Filter -- make sure it is of rank 1 +// get rank of Input & Filter -- make sure it is of rank 1 mlir::LogicalResult FIRFilterResponseOp::verify() { // auto inputType = llvm::dyn_cast(getOperand(0).getType()); - // auto filterType = llvm::dyn_cast(getOperand(1).getType()); + // auto filterType = + // llvm::dyn_cast(getOperand(1).getType()); // // auto resultType = llvm::dyn_cast(getType()); // auto inputRank = inputType.getRank(); @@ -670,41 +699,39 @@ mlir::LogicalResult FIRFilterResponseOp::verify() { // } return mlir::success(); -} - +} //===----------------------------------------------------------------------===// // SlidingWindowAvgOp //===----------------------------------------------------------------------===// -void SlidingWindowAvgOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value value) { +void SlidingWindowAvgOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value value) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands(value); } void SlidingWindowAvgOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - 2 + // for each rank + // Get the shape/size of input + // output size = input_size - 2 auto inputType = llvm::dyn_cast(getOperand().getType()); auto shapeOfInput = inputType.getShape(); std::vector shapeForOutput; - //Iterate for each rank : tensor<1x2x3x2> = rank 4 - for(size_t i=0; i < shapeOfInput.size() ; i++){ + // Iterate for each rank : tensor<1x2x3x2> = rank 4 + for (size_t i = 0; i < shapeOfInput.size(); i++) { shapeForOutput.push_back(shapeOfInput[i] - 2); } - mlir::TensorType outputType = mlir::RankedTensorType::get(shapeForOutput, - getInput().getType().getElementType()); - // getOperand().getType()); - // getOperand().getType().getElementType()); + mlir::TensorType outputType = mlir::RankedTensorType::get( + shapeForOutput, getInput().getType().getElementType()); + // getOperand().getType()); + // getOperand().getType().getElementType()); getResult().setType(outputType); - } mlir::LogicalResult SlidingWindowAvgOp::verify() { @@ -719,7 +746,8 @@ mlir::LogicalResult SlidingWindowAvgOp::verify() { // for(size_t i=0; i < shapeOfInput.size() ; i++){ // if(shapeOfInput[i] < 3){ - // llvm::errs() << "Warning:SlidingWindowAvgOp = Input size < 3 " << "size= " << shapeOfInput[i] << "\n" ; + // llvm::errs() << "Warning:SlidingWindowAvgOp = Input size < 3 " << + // "size= " << shapeOfInput[i] << "\n" ; // } // } @@ -730,221 +758,226 @@ mlir::LogicalResult SlidingWindowAvgOp::verify() { // DownsamplingOp //===----------------------------------------------------------------------===// -void DownsamplingOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs) { +void DownsamplingOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value lhs, + mlir::Value rhs) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands({lhs, rhs}); } - - -/// Infer the output shape of the DownsamplingOp, this is required by the shape inference -/// interface. -//ToDo -- shape should be the length of Lhs + Rhs - 1 -void DownsamplingOp::inferShapes() { - //get the shape of Lhs & rhs - //add the shape for each dimension - // auto tensorInput = llvm::cast(getLhs().getType()); - auto tensorInput = getLhs().getType(); +/// Infer the output shape of the DownsamplingOp, this is required by the shape +/// inference interface. +// ToDo -- shape should be the length of Lhs + Rhs - 1 +void DownsamplingOp::inferShapes() { + // get the shape of Lhs & rhs + // add the shape for each dimension + // auto tensorInput = llvm::cast(getLhs().getType()); + auto tensorInput = getLhs().getType(); auto shapeOfInput = tensorInput.getShape(); - // auto tensorDownsampling = getRhs().getType(); - // auto shapeOfDownsampling = tensorDownsampling.getShape(); //shape is the dimension - + // auto tensorDownsampling = getRhs().getType(); + // auto shapeOfDownsampling = tensorDownsampling.getShape(); //shape is the + // dimension - std::vector shapeForOutput ; + std::vector shapeForOutput; int64_t SecondValueInt = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int Value downsampling2ndArg = getOperand(1); - dsp::ConstantOp constantOp2ndArg = downsampling2ndArg.getDefiningOp(); - DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue();; + dsp::ConstantOp constantOp2ndArg = + downsampling2ndArg.getDefiningOp(); + DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue(); + ; auto elements = constantRhsValue.getValues(); float SecondValue = elements[0].getValueAsDouble(); - SecondValueInt = (int64_t) SecondValue; - // llvm::errs() << "Downsampling: SamplingRate: " << SecondValueInt << " \n"; //downsamplingRate - - - for(size_t i=0; i < shapeOfInput.size() ; i++){ - double GetLenForOutput = static_cast(shapeOfInput[i] )/ SecondValueInt ; - if(fmod(GetLenForOutput, 1.0) != 0) { - //if remainder remains + SecondValueInt = (int64_t)SecondValue; + // llvm::errs() << "Downsampling: SamplingRate: " << SecondValueInt << " \n"; + // //downsamplingRate + + for (size_t i = 0; i < shapeOfInput.size(); i++) { + double GetLenForOutput = + static_cast(shapeOfInput[i]) / SecondValueInt; + if (fmod(GetLenForOutput, 1.0) != 0) { + // if remainder remains GetLenForOutput = ceil(GetLenForOutput); } - int64_t OutlenInt = static_cast (GetLenForOutput); + int64_t OutlenInt = static_cast(GetLenForOutput); llvm::errs() << "Downsampling: OutlenInt: " << OutlenInt << " \n"; shapeForOutput.push_back(OutlenInt); } - - mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - getLhs().getType().getElementType()); - // getResult().setType(getLhs().getType()); + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getLhs().getType().getElementType()); + + // getResult().setType(getLhs().getType()); getResult().setType(manipulatedType); - } +} -//get rank of Input & Downsampling -- make sure it is of rank 1 +// get rank of Input & Downsampling -- make sure it is of rank 1 mlir::LogicalResult DownsamplingOp::verify() { // auto inputType = llvm::dyn_cast(getOperand(0).getType()); - // auto samplingRateType = llvm::dyn_cast(getOperand(1).getType()); + // auto samplingRateType = + // llvm::dyn_cast(getOperand(1).getType()); // // auto resultType = llvm::dyn_cast(getType()); // auto inputRank = inputType.getRank(); // auto samplingRateRank = samplingRateType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " samplingRateRank: " << samplingRateRank << "\n"; - // //once ensured only 1 rank from above -- also make sure there is just 1 elem - // if( inputRank != 1 || samplingRateRank != 0 ) + // // llvm::errs() << "inputRank: " << inputRank << " samplingRateRank: " << + // samplingRateRank << "\n"; + // //once ensured only 1 rank from above -- also make sure there is just 1 + // elem if( inputRank != 1 || samplingRateRank != 0 ) // { - // llvm::errs() << "inputRank: " << inputRank << " samplingRateRank: " << samplingRateRank << "\n"; - // return emitError() + // llvm::errs() << "inputRank: " << inputRank << " samplingRateRank: " << + // samplingRateRank << "\n"; return emitError() // << "expected rank of input & Downsampling is 1"; // } return mlir::success(); -} +} //===----------------------------------------------------------------------===// // UpsamplingOp //===----------------------------------------------------------------------===// void UpsamplingOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs) { + mlir::Value lhs, mlir::Value rhs) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands({lhs, rhs}); } - - -/// Infer the output shape of the UpsamplingOp, this is required by the shape inference -/// interface. -//ToDo -- shape should be the length of input * UpsamplingRate ie, Rhs -void UpsamplingOp::inferShapes() { - //get the shape of Lhs & rhs - //add the shape for each dimension - // auto tensorInput = llvm::cast(getLhs().getType()); - auto tensorInput = getLhs().getType(); +/// Infer the output shape of the UpsamplingOp, this is required by the shape +/// inference interface. +// ToDo -- shape should be the length of input * UpsamplingRate ie, Rhs +void UpsamplingOp::inferShapes() { + // get the shape of Lhs & rhs + // add the shape for each dimension + // auto tensorInput = llvm::cast(getLhs().getType()); + auto tensorInput = getLhs().getType(); auto shapeOfInput = tensorInput.getShape(); - // auto tensorUpsampling = getRhs().getType(); + // auto tensorUpsampling = getRhs().getType(); // auto shapeOfUpsampling = tensorUpsampling.getShape(); //shape is the length - - std::vector shapeForOutput ; + std::vector shapeForOutput; int64_t SecondValueInt = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int Value upsampling2ndArg = getOperand(1); - dsp::ConstantOp constantOp2ndArg = upsampling2ndArg.getDefiningOp(); - DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue();; + dsp::ConstantOp constantOp2ndArg = + upsampling2ndArg.getDefiningOp(); + DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue(); + ; auto elements = constantRhsValue.getValues(); float SecondValue = elements[0].getValueAsDouble(); - SecondValueInt = (int64_t) SecondValue; - // llvm::errs() << "Upsampling: SamplingRate: " << SecondValueInt << " \n"; //downsamplingRate - - - for(size_t i=0; i < shapeOfInput.size() ; i++){ - double GetLenForOutput = static_cast(shapeOfInput[i] ) * SecondValueInt ; - int64_t OutlenInt = static_cast (GetLenForOutput); + SecondValueInt = (int64_t)SecondValue; + // llvm::errs() << "Upsampling: SamplingRate: " << SecondValueInt << " \n"; + // //downsamplingRate + + for (size_t i = 0; i < shapeOfInput.size(); i++) { + double GetLenForOutput = + static_cast(shapeOfInput[i]) * SecondValueInt; + int64_t OutlenInt = static_cast(GetLenForOutput); llvm::errs() << "Upsampling: OutlenInt: " << OutlenInt << " \n"; shapeForOutput.push_back(OutlenInt); } - - mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - getLhs().getType().getElementType()); - // getResult().setType(getLhs().getType()); + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getLhs().getType().getElementType()); + + // getResult().setType(getLhs().getType()); getResult().setType(manipulatedType); - } +} -//get rank of Input & Upsampling -- make sure it is of rank 1 +// get rank of Input & Upsampling -- make sure it is of rank 1 mlir::LogicalResult UpsamplingOp::verify() { // auto inputType = llvm::dyn_cast(getOperand(0).getType()); - // auto samplingRateType = llvm::dyn_cast(getOperand(1).getType()); + // auto samplingRateType = + // llvm::dyn_cast(getOperand(1).getType()); // // auto resultType = llvm::dyn_cast(getType()); // auto inputRank = inputType.getRank(); // auto samplingRateRank = samplingRateType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " samplingRateRank: " << samplingRateRank << "\n"; - // //once ensured only 1 rank from above -- also make sure there is just 1 elem - // if( inputRank != 1 || samplingRateRank != 0 ) + // // llvm::errs() << "inputRank: " << inputRank << " samplingRateRank: " << + // samplingRateRank << "\n"; + // //once ensured only 1 rank from above -- also make sure there is just 1 + // elem if( inputRank != 1 || samplingRateRank != 0 ) // { - // llvm::errs() << "inputRank: " << inputRank << " samplingRateRank: " << samplingRateRank << "\n"; - // return emitError() + // llvm::errs() << "inputRank: " << inputRank << " samplingRateRank: " << + // samplingRateRank << "\n"; return emitError() // << "expected rank of input is 1 & Upsampling is 0"; // } return mlir::success(); -} - +} //===----------------------------------------------------------------------===// // LowPassFilter1stOrderOp //===----------------------------------------------------------------------===// -void LowPassFilter1stOrderOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs) { +void LowPassFilter1stOrderOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, + mlir::Value lhs, mlir::Value rhs) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands({lhs, rhs}); } - - -/// Infer the output shape of the LowPassFilter1stOrderOp, this is required by the shape inference -/// interface. -void LowPassFilter1stOrderOp::inferShapes() { - //get the shape of Lhs & rhs - // auto tensorInput = llvm::cast(getLhs().getType()); - auto tensorInput = getLhs().getType(); +/// Infer the output shape of the LowPassFilter1stOrderOp, this is required by +/// the shape inference interface. +void LowPassFilter1stOrderOp::inferShapes() { + // get the shape of Lhs & rhs + // auto tensorInput = llvm::cast(getLhs().getType()); + auto tensorInput = getLhs().getType(); getResult().setType(tensorInput); } -//get rank of Input & alphaValue -- make sure it is of rank 1 +// get rank of Input & alphaValue -- make sure it is of rank 1 mlir::LogicalResult LowPassFilter1stOrderOp::verify() { // auto inputType = llvm::dyn_cast(getOperand(0).getType()); - // auto alphaValueType = llvm::dyn_cast(getOperand(1).getType()); + // auto alphaValueType = + // llvm::dyn_cast(getOperand(1).getType()); // // auto resultType = llvm::dyn_cast(getType()); // auto inputRank = inputType.getRank(); // auto alphaValueRank = alphaValueType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // //once ensured only 1 rank from above -- also make sure there is just 1 elem - // if( inputRank != 1 || alphaValueRank != 0 ) + // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // //once ensured only 1 rank from above -- also make sure there is just 1 + // elem if( inputRank != 1 || alphaValueRank != 0 ) // { - // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // return emitError() + // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; return emitError() // << "expected rank of input & Upsampling is 1"; // } return mlir::success(); -} +} //===----------------------------------------------------------------------===// // HighPassFilterOp //===----------------------------------------------------------------------===// -void HighPassFilterOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value value) { +void HighPassFilterOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value value) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands(value); } void HighPassFilterOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - auto tensorInput = getInput().getType(); + // for each rank + // Get the shape/size of input + // output size = input_size + auto tensorInput = getInput().getType(); getResult().setType(tensorInput); - } mlir::LogicalResult HighPassFilterOp::verify() { @@ -952,7 +985,7 @@ mlir::LogicalResult HighPassFilterOp::verify() { // auto inputRank = inputType.getRank(); // llvm::errs() << "inputRank: " << inputRank << "\n"; - // //once ensured only 1 rank from above -- + // //once ensured only 1 rank from above -- // if( inputRank != 1 ) // { // llvm::errs() << "inputRank: " << inputRank << "\n"; @@ -962,25 +995,24 @@ mlir::LogicalResult HighPassFilterOp::verify() { return mlir::success(); } - //===----------------------------------------------------------------------===// // FFT1DOp //===----------------------------------------------------------------------===// void FFT1DOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value value) { - DEBUG_PRINT_NO_ARGS() ; - state.addTypes({UnrankedTensorType::get(builder.getF64Type()), - UnrankedTensorType::get(builder.getF64Type())}); + mlir::Value value) { + DEBUG_PRINT_NO_ARGS(); + state.addTypes({UnrankedTensorType::get(builder.getF64Type()), + UnrankedTensorType::get(builder.getF64Type())}); state.addOperands(value); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); } void FFT1DOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - auto tensorInput = getInput().getType(); + // for each rank + // Get the shape/size of input + // output size = input_size + auto tensorInput = getInput().getType(); // getResult().setType(tensorInput); getResult(0).setType(tensorInput); getResult(1).setType(tensorInput); @@ -988,12 +1020,13 @@ void FFT1DOp::inferShapes() { } mlir::LogicalResult FFT1DOp::verify() { - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); // auto inputType = llvm::dyn_cast(getOperand().getType()); // auto inputRank = inputType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // //once ensured only 1 rank from above -- + // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // //once ensured only 1 rank from above -- // if( inputRank != 1 ) // { // llvm::errs() << "inputRank: " << inputRank << "\n"; @@ -1003,36 +1036,36 @@ mlir::LogicalResult FFT1DOp::verify() { return mlir::success(); } - //===----------------------------------------------------------------------===// // IFFT1DOp //===----------------------------------------------------------------------===// void IFFT1DOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value real, mlir::Value img) { - DEBUG_PRINT_NO_ARGS() ; + mlir::Value real, mlir::Value img) { + DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); - state.addOperands({real , img}); - DEBUG_PRINT_NO_ARGS() ; + state.addOperands({real, img}); + DEBUG_PRINT_NO_ARGS(); } void IFFT1DOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - auto tensorInput = getReal().getType(); + // for each rank + // Get the shape/size of input + // output size = input_size + auto tensorInput = getReal().getType(); getResult().setType(tensorInput); // getResult(0).setType(tensorInput); // getResult(1).setType(tensorInput); } mlir::LogicalResult IFFT1DOp::verify() { - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); // auto inputType = llvm::dyn_cast(getOperand().getType()); // auto inputRank = inputType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // //once ensured only 1 rank from above -- + // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // //once ensured only 1 rank from above -- // if( inputRank != 1 ) // { // llvm::errs() << "inputRank: " << inputRank << "\n"; @@ -1046,55 +1079,54 @@ mlir::LogicalResult IFFT1DOp::verify() { // HammingWindowOp //===----------------------------------------------------------------------===// -void HammingWindowOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value value) { +void HammingWindowOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value value) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands(value); } void HammingWindowOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - // auto inputType = llvm::dyn_cast(getOperand().getType()); + // for each rank + // Get the shape/size of input + // output size = input_size + // auto inputType = llvm::dyn_cast(getOperand().getType()); // auto shapeOfInput = inputType.getShape(); - std::vector shapeForOutput ; + std::vector shapeForOutput; int64_t FirstOpInt = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int // llvm::errs() << "LINE " << __LINE__ << " file= " << __FILE__ << "\n" ; Value hammingLen = getOperand(); - dsp::ConstantOp constantOp1stArg = hammingLen.getDefiningOp(); + dsp::ConstantOp constantOp1stArg = + hammingLen.getDefiningOp(); // llvm::errs() << "LINE " << __LINE__ << " file= " << __FILE__ << "\n" ; DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); // llvm::errs() << "LINE " << __LINE__ << " file= " << __FILE__ << "\n" ; auto elements = constantLhsValue.getValues(); float FirstValue = elements[0].getValueAsDouble(); - FirstOpInt = (int64_t) FirstValue; + FirstOpInt = (int64_t)FirstValue; // llvm::errs() << "FirstOpInt " << FirstOpInt << "\n" ; // llvm::errs() << "shapeOfInput.size() " << shapeOfInput.size() << "\n" ; // for(size_t i=0; i < shapeOfInput.size() ; i++){ - // llvm::errs() << "LINE " << __LINE__ << " file= " << __FILE__ << "\n" ; - shapeForOutput.push_back(FirstOpInt); + // llvm::errs() << "LINE " << __LINE__ << " file= " << __FILE__ << "\n" ; + shapeForOutput.push_back(FirstOpInt); // } - mlir::TensorType outputType = mlir::RankedTensorType::get(shapeForOutput, - getInput().getType().getElementType()); - // getOperand().getType()); - // getOperand().getType().getElementType()); + mlir::TensorType outputType = mlir::RankedTensorType::get( + shapeForOutput, getInput().getType().getElementType()); + // getOperand().getType()); + // getOperand().getType().getElementType()); getResult().setType(outputType); // llvm::errs() << "LINE " << __LINE__ << " file= " << __FILE__ << "\n" ; - - } mlir::LogicalResult HammingWindowOp::verify() { @@ -1109,7 +1141,8 @@ mlir::LogicalResult HammingWindowOp::verify() { // for(size_t i=0; i < shapeOfInput.size() ; i++){ // if(shapeOfInput[i] < 3){ - // llvm::errs() << "Warning:HammingWindowOp = Input size < 3 " << "size= " << shapeOfInput[i] << "\n" ; + // llvm::errs() << "Warning:HammingWindowOp = Input size < 3 " << "size= " + // << shapeOfInput[i] << "\n" ; // } // } @@ -1121,7 +1154,7 @@ mlir::LogicalResult HammingWindowOp::verify() { //===----------------------------------------------------------------------===// void DCTOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value value) { + mlir::Value value) { // DEBUG_PRINT_NO_ARGS() ; state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands(value); @@ -1129,10 +1162,10 @@ void DCTOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, } void DCTOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - auto tensorInput = getInput().getType(); + // for each rank + // Get the shape/size of input + // output size = input_size + auto tensorInput = getInput().getType(); getResult().setType(tensorInput); // getResult(0).setType(tensorInput); // getResult(1).setType(tensorInput); @@ -1143,46 +1176,42 @@ mlir::LogicalResult DCTOp::verify() { auto inputType = llvm::dyn_cast(getOperand().getType()); auto inputRank = inputType.getRank(); - // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - //once ensured only 1 rank from above -- - if( inputRank != 1 ) - { - llvm::errs() << "inputRank: " << inputRank << "\n"; - return emitError() - << "expected rank of input is 1"; + // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // once ensured only 1 rank from above -- + if (inputRank != 1) { + llvm::errs() << "inputRank: " << inputRank << "\n"; + return emitError() << "expected rank of input is 1"; } return mlir::success(); } - - //===----------------------------------------------------------------------===// // filterOp //===----------------------------------------------------------------------===// void filterOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value b, mlir::Value a, mlir::Value x) { + mlir::Value b, mlir::Value a, mlir::Value x) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands({b, a, x}); } +/// Infer the output shape of the filterOp, this is required by the shape +/// inference interface. +// ToDo -- shape should be the length of Lhs + Rhs - 1 +void filterOp::inferShapes() { + // get the shape of Lhs & rhs + // add the shape for each dimension + // auto tensorInput = llvm::cast(getLhs().getType()); + auto tensorInput = getX().getType(); + getResult().setType(tensorInput); +} - -/// Infer the output shape of the filterOp, this is required by the shape inference -/// interface. -//ToDo -- shape should be the length of Lhs + Rhs - 1 -void filterOp::inferShapes() { - //get the shape of Lhs & rhs - //add the shape for each dimension - // auto tensorInput = llvm::cast(getLhs().getType()); - auto tensorInput = getX().getType(); - getResult().setType(tensorInput ); - } - -//get rank of Input & Filter -- make sure it is of rank 1 +// get rank of Input & Filter -- make sure it is of rank 1 mlir::LogicalResult filterOp::verify() { // auto inputType = llvm::dyn_cast(getOperand(0).getType()); - // auto filterType = llvm::dyn_cast(getOperand(1).getType()); + // auto filterType = + // llvm::dyn_cast(getOperand(1).getType()); // // auto resultType = llvm::dyn_cast(getType()); // auto inputRank = inputType.getRank(); @@ -1195,15 +1224,14 @@ mlir::LogicalResult filterOp::verify() { // } return mlir::success(); -} - +} //===----------------------------------------------------------------------===// // SumOp //===----------------------------------------------------------------------===// void SumOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value value) { + mlir::Value value) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands(value); } @@ -1215,8 +1243,8 @@ void SumOp::inferShapes() { shapeForOutput.push_back(1); - mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - getInput().getType().getElementType()); + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getInput().getType().getElementType()); getResult().setType(manipulatedType); } @@ -1235,96 +1263,99 @@ mlir::LogicalResult SumOp::verify() { return mlir::success(); } - //===----------------------------------------------------------------------===// - // CosOp - //===----------------------------------------------------------------------===// - - void CosOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value value) { - // DEBUG_PRINT_NO_ARGS() ; - state.addTypes(UnrankedTensorType::get(builder.getF64Type())); - state.addOperands(value); - // DEBUG_PRINT_NO_ARGS() ; - } - - void CosOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - auto tensorInput = getInput().getType(); - getResult().setType(tensorInput); - // getResult(0).setType(tensorInput); - // getResult(1).setType(tensorInput); - } - - mlir::LogicalResult CosOp::verify() { - // DEBUG_PRINT_NO_ARGS() ; +//===----------------------------------------------------------------------===// +// CosOp +//===----------------------------------------------------------------------===// + +void CosOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value value) { + // DEBUG_PRINT_NO_ARGS() ; + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands(value); + // DEBUG_PRINT_NO_ARGS() ; +} + +void CosOp::inferShapes() { + // for each rank + // Get the shape/size of input + // output size = input_size + auto tensorInput = getInput().getType(); + getResult().setType(tensorInput); + // getResult(0).setType(tensorInput); + // getResult(1).setType(tensorInput); +} + +mlir::LogicalResult CosOp::verify() { + // DEBUG_PRINT_NO_ARGS() ; // auto inputType = llvm::dyn_cast(getOperand().getType()); // auto inputRank = inputType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // //once ensured only 1 rank from above -- + // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // //once ensured only 1 rank from above -- // if( inputRank != 1 ) // { // llvm::errs() << "inputRank: " << inputRank << "\n"; // return emitError() // << "expected rank of input is 1"; // } - return mlir::success(); - } - - //===----------------------------------------------------------------------===// - // SinOp - //===----------------------------------------------------------------------===// - - void SinOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value value) { - // DEBUG_PRINT_NO_ARGS() ; - state.addTypes(UnrankedTensorType::get(builder.getF64Type())); - state.addOperands(value); - // DEBUG_PRINT_NO_ARGS() ; - } - - void SinOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - auto tensorInput = getInput().getType(); - getResult().setType(tensorInput); - // getResult(0).setType(tensorInput); - // getResult(1).setType(tensorInput); - } - - mlir::LogicalResult SinOp::verify() { - // DEBUG_PRINT_NO_ARGS() ; + return mlir::success(); +} + +//===----------------------------------------------------------------------===// +// SinOp +//===----------------------------------------------------------------------===// + +void SinOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value value) { + // DEBUG_PRINT_NO_ARGS() ; + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands(value); + // DEBUG_PRINT_NO_ARGS() ; +} + +void SinOp::inferShapes() { + // for each rank + // Get the shape/size of input + // output size = input_size + auto tensorInput = getInput().getType(); + getResult().setType(tensorInput); + // getResult(0).setType(tensorInput); + // getResult(1).setType(tensorInput); +} + +mlir::LogicalResult SinOp::verify() { + // DEBUG_PRINT_NO_ARGS() ; // auto inputType = llvm::dyn_cast(getOperand().getType()); // auto inputRank = inputType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // //once ensured only 1 rank from above -- + // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // //once ensured only 1 rank from above -- // if( inputRank != 1 ) // { // llvm::errs() << "inputRank: " << inputRank << "\n"; // return emitError() // << "expected rank of input is 1"; // } - return mlir::success(); - } + return mlir::success(); +} //===----------------------------------------------------------------------===// // SquareOp //===----------------------------------------------------------------------===// void SquareOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value value) { + mlir::Value value) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands(value); } void SquareOp::inferShapes() { - auto tensorInput = getInput().getType(); - // mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - // getInput().getType().getElementType()); + auto tensorInput = getInput().getType(); + // mlir::TensorType manipulatedType = + // mlir::RankedTensorType::get(shapeForOutput, + // getInput().getType().getElementType()); getResult().setType(tensorInput); } @@ -1349,17 +1380,17 @@ mlir::LogicalResult SquareOp::verify() { void FFT1DRealOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, mlir::Value value) { - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); state.addOperands(value); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); } void FFT1DRealOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - auto tensorInput = getInput().getType(); + // for each rank + // Get the shape/size of input + // output size = input_size + auto tensorInput = getInput().getType(); // getResult().setType(tensorInput); getResult().setType(tensorInput); // getResult(2).setType(tensorInput); @@ -1370,8 +1401,9 @@ mlir::LogicalResult FFT1DRealOp::verify() { // auto inputType = llvm::dyn_cast(getOperand().getType()); // auto inputRank = inputType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // //once ensured only 1 rank from above -- + // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // //once ensured only 1 rank from above -- // if( inputRank != 1 ) // { // llvm::errs() << "inputRank: " << inputRank << "\n"; @@ -1386,30 +1418,31 @@ mlir::LogicalResult FFT1DRealOp::verify() { //===----------------------------------------------------------------------===// void FFT1DImgOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value value) { - DEBUG_PRINT_NO_ARGS() ; + mlir::Value value) { + DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); state.addOperands(value); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); } void FFT1DImgOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - auto tensorInput = getInput().getType(); + // for each rank + // Get the shape/size of input + // output size = input_size + auto tensorInput = getInput().getType(); // getResult().setType(tensorInput); getResult().setType(tensorInput); // getResult(2).setType(tensorInput); } mlir::LogicalResult FFT1DImgOp::verify() { - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); // auto inputType = llvm::dyn_cast(getOperand().getType()); // auto inputRank = inputType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // //once ensured only 1 rank from above -- + // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // //once ensured only 1 rank from above -- // if( inputRank != 1 ) // { // llvm::errs() << "inputRank: " << inputRank << "\n"; @@ -1424,30 +1457,30 @@ mlir::LogicalResult FFT1DImgOp::verify() { //===----------------------------------------------------------------------===// void SincOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value wc, mlir::Value n) { - DEBUG_PRINT_NO_ARGS() ; + mlir::Value wc, mlir::Value n) { + DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); state.addOperands({wc, n}); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); } void SincOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - // auto inputType = llvm::dyn_cast(getN().getType()); + // for each rank + // Get the shape/size of input + // output size = input_size + // auto inputType = llvm::dyn_cast(getN().getType()); // auto shapeOfInput = inputType.getShape(); - std::vector shapeForOutput ; + std::vector shapeForOutput; int64_t GetLen = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int DEBUG_PRINT_NO_ARGS(); Value inputLen = getOperand(1); dsp::ConstantOp constantOp1stArg = inputLen.getDefiningOp(); @@ -1455,26 +1488,25 @@ void SincOp::inferShapes() { DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); auto elements = constantLhsValue.getValues(); float LenN = elements[0].getValueAsDouble(); - GetLen = (int64_t) LenN; + GetLen = (int64_t)LenN; DEBUG_PRINT_WITH_ARGS(GetLen); - DEBUG_PRINT_WITH_ARGS("GetLen= " , GetLen); + DEBUG_PRINT_WITH_ARGS("GetLen= ", GetLen); shapeForOutput.push_back(GetLen); - mlir::TensorType outputType = mlir::RankedTensorType::get(shapeForOutput, - getWc().getType().getElementType()); - + mlir::TensorType outputType = mlir::RankedTensorType::get( + shapeForOutput, getWc().getType().getElementType()); getResult().setType(outputType); - } mlir::LogicalResult SincOp::verify() { - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); // auto inputType = llvm::dyn_cast(getOperand().getType()); // auto inputRank = inputType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // //once ensured only 1 rank from above -- + // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // //once ensured only 1 rank from above -- // if( inputRank != 1 ) // { // llvm::errs() << "inputRank: " << inputRank << "\n"; @@ -1488,11 +1520,12 @@ mlir::LogicalResult SincOp::verify() { // GetElemAtIndxOp //===----------------------------------------------------------------------===// -void GetElemAtIndxOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value input, mlir::Value indx) { +void GetElemAtIndxOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value input, + mlir::Value indx) { DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); - state.addOperands({input, indx} ); + state.addOperands({input, indx}); DEBUG_PRINT_NO_ARGS(); } @@ -1503,8 +1536,8 @@ void GetElemAtIndxOp::inferShapes() { DEBUG_PRINT_NO_ARGS(); shapeForOutput.push_back(1); - mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - getInput().getType().getElementType()); + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getInput().getType().getElementType()); getResult().setType(manipulatedType); DEBUG_PRINT_NO_ARGS(); } @@ -1524,16 +1557,16 @@ mlir::LogicalResult GetElemAtIndxOp::verify() { return mlir::success(); } - //===----------------------------------------------------------------------===// // SetElemAtIndxOp //===----------------------------------------------------------------------===// -void SetElemAtIndxOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value input, mlir::Value indx, mlir::Value val) { +void SetElemAtIndxOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value input, + mlir::Value indx, mlir::Value val) { DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); - state.addOperands({input, indx, val} ); + state.addOperands({input, indx, val}); DEBUG_PRINT_NO_ARGS(); } @@ -1544,45 +1577,44 @@ void SetElemAtIndxOp::inferShapes() { DEBUG_PRINT_NO_ARGS(); shapeForOutput.push_back(1); - mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - getInput().getType().getElementType()); + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getInput().getType().getElementType()); getResult().setType(manipulatedType); DEBUG_PRINT_NO_ARGS(); } -mlir::LogicalResult SetElemAtIndxOp::verify() { - return mlir::success(); -} +mlir::LogicalResult SetElemAtIndxOp::verify() { return mlir::success(); } //===----------------------------------------------------------------------===// // LowPassFIRFilterOp //===----------------------------------------------------------------------===// -void LowPassFIRFilterOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value wc, mlir::Value n) { - DEBUG_PRINT_NO_ARGS() ; +void LowPassFIRFilterOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value wc, + mlir::Value n) { + DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); state.addOperands({wc, n}); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); } void LowPassFIRFilterOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - // auto inputType = llvm::dyn_cast(getN().getType()); + // for each rank + // Get the shape/size of input + // output size = input_size + // auto inputType = llvm::dyn_cast(getN().getType()); // auto shapeOfInput = inputType.getShape(); - std::vector shapeForOutput ; + std::vector shapeForOutput; uint64_t GetLen = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int DEBUG_PRINT_NO_ARGS(); Value inputLen = getOperand(1); dsp::ConstantOp constantOp1stArg = inputLen.getDefiningOp(); @@ -1590,30 +1622,27 @@ void LowPassFIRFilterOp::inferShapes() { DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); auto elements = constantLhsValue.getValues(); float LenN = elements[0].getValueAsDouble(); - GetLen = (uint64_t) LenN; + GetLen = (uint64_t)LenN; DEBUG_PRINT_WITH_ARGS(GetLen); - DEBUG_PRINT_WITH_ARGS("GetLen= " , GetLen); - - //int64_t N = tensorType.getShape()[0]; + DEBUG_PRINT_WITH_ARGS("GetLen= ", GetLen); + // int64_t N = tensorType.getShape()[0]; shapeForOutput.push_back(GetLen); - mlir::TensorType outputType = mlir::RankedTensorType::get(shapeForOutput, - getWc().getType().getElementType()); - + mlir::TensorType outputType = mlir::RankedTensorType::get( + shapeForOutput, getWc().getType().getElementType()); getResult().setType(outputType); - } mlir::LogicalResult LowPassFIRFilterOp::verify() { uint64_t GetLen = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int DEBUG_PRINT_NO_ARGS(); Value inputLen = getOperand(1); dsp::ConstantOp constantOp1stArg = inputLen.getDefiningOp(); @@ -1621,17 +1650,16 @@ mlir::LogicalResult LowPassFIRFilterOp::verify() { DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); auto elements = constantLhsValue.getValues(); float LenN = elements[0].getValueAsDouble(); - GetLen = (uint64_t) LenN; + GetLen = (uint64_t)LenN; DEBUG_PRINT_WITH_ARGS(GetLen); - DEBUG_PRINT_WITH_ARGS("GetLen= " , GetLen); - - //filter-order even not supported -- so making it odd - if(GetLen % 2 == 0 ) - { + DEBUG_PRINT_WITH_ARGS("GetLen= ", GetLen); + + // filter-order even not supported -- so making it odd + if (GetLen % 2 == 0) { // GetLen = GetLen + 1; llvm::errs() << "N for lowPassFilter must be odd but is " << GetLen << "\n"; - // DEBUG_PRINT_WITH_ARGS("Making LowPassFilterLen Odd= " , GetLen); - return mlir::failure(); + // DEBUG_PRINT_WITH_ARGS("Making LowPassFilterLen Odd= " , GetLen); + return mlir::failure(); } return mlir::success(); } @@ -1641,19 +1669,20 @@ mlir::LogicalResult LowPassFIRFilterOp::verify() { //===----------------------------------------------------------------------===// void LMSFilterOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs, mlir::Value mu, mlir::Value filterLen, mlir::Value iters) { - + mlir::Value lhs, mlir::Value rhs, mlir::Value mu, + mlir::Value filterLen, mlir::Value iters) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); - state.addOperands({lhs, rhs, mu, filterLen, iters}); + state.addOperands({lhs, rhs, mu, filterLen, iters}); } - void LMSFilterOp::inferShapes() { getResult().setType(getLhs().getType()); } mlir::LogicalResult LMSFilterOp::verify() { - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); // auto inputType = llvm::dyn_cast(getOperand(0).getType()); - // auto filterType = llvm::dyn_cast(getOperand(1).getType()); + // auto filterType = + // llvm::dyn_cast(getOperand(1).getType()); // // auto resultType = llvm::dyn_cast(getType()); // auto inputRank = inputType.getRank(); @@ -1668,36 +1697,36 @@ mlir::LogicalResult LMSFilterOp::verify() { return mlir::success(); } - //===----------------------------------------------------------------------===// // HighPassFIRFilterOp //===----------------------------------------------------------------------===// -void HighPassFIRFilterOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value wc, mlir::Value n) { - DEBUG_PRINT_NO_ARGS() ; +void HighPassFIRFilterOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value wc, + mlir::Value n) { + DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); state.addOperands({wc, n}); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); } void HighPassFIRFilterOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - // auto inputType = llvm::dyn_cast(getN().getType()); + // for each rank + // Get the shape/size of input + // output size = input_size + // auto inputType = llvm::dyn_cast(getN().getType()); // auto shapeOfInput = inputType.getShape(); - std::vector shapeForOutput ; + std::vector shapeForOutput; int64_t GetLen = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int DEBUG_PRINT_NO_ARGS(); Value inputLen = getOperand(1); dsp::ConstantOp constantOp1stArg = inputLen.getDefiningOp(); @@ -1705,26 +1734,25 @@ void HighPassFIRFilterOp::inferShapes() { DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); auto elements = constantLhsValue.getValues(); float LenN = elements[0].getValueAsDouble(); - GetLen = (int64_t) LenN; + GetLen = (int64_t)LenN; DEBUG_PRINT_WITH_ARGS(GetLen); - DEBUG_PRINT_WITH_ARGS("GetLen= " , GetLen); + DEBUG_PRINT_WITH_ARGS("GetLen= ", GetLen); shapeForOutput.push_back(GetLen); - mlir::TensorType outputType = mlir::RankedTensorType::get(shapeForOutput, - getWc().getType().getElementType()); - + mlir::TensorType outputType = mlir::RankedTensorType::get( + shapeForOutput, getWc().getType().getElementType()); getResult().setType(outputType); - } mlir::LogicalResult HighPassFIRFilterOp::verify() { - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); // auto inputType = llvm::dyn_cast(getOperand().getType()); // auto inputRank = inputType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // //once ensured only 1 rank from above -- + // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // //once ensured only 1 rank from above -- // if( inputRank != 1 ) // { // llvm::errs() << "inputRank: " << inputRank << "\n"; @@ -1738,31 +1766,32 @@ mlir::LogicalResult HighPassFIRFilterOp::verify() { // GetRangeOfVectorOp //===----------------------------------------------------------------------===// -void GetRangeOfVectorOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value first, mlir::Value N, mlir::Value step) { - DEBUG_PRINT_NO_ARGS() ; +void GetRangeOfVectorOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value first, + mlir::Value N, mlir::Value step) { + DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); state.addOperands({first, N, step}); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); } void GetRangeOfVectorOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - // auto inputType = llvm::dyn_cast(getN().getType()); + // for each rank + // Get the shape/size of input + // output size = input_size + // auto inputType = llvm::dyn_cast(getN().getType()); // auto shapeOfInput = inputType.getShape(); - std::vector shapeForOutput ; + std::vector shapeForOutput; int64_t GetLen = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int DEBUG_PRINT_NO_ARGS(); Value inputLen = getOperand(1); dsp::ConstantOp constantOp1stArg = inputLen.getDefiningOp(); @@ -1770,26 +1799,25 @@ void GetRangeOfVectorOp::inferShapes() { DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); auto elements = constantLhsValue.getValues(); float LenN = elements[0].getValueAsDouble(); - GetLen = (int64_t) LenN; + GetLen = (int64_t)LenN; DEBUG_PRINT_WITH_ARGS(GetLen); - DEBUG_PRINT_WITH_ARGS("GetLen= " , GetLen); + DEBUG_PRINT_WITH_ARGS("GetLen= ", GetLen); shapeForOutput.push_back(GetLen); - mlir::TensorType outputType = mlir::RankedTensorType::get(shapeForOutput, - getFirst().getType().getElementType()); - + mlir::TensorType outputType = mlir::RankedTensorType::get( + shapeForOutput, getFirst().getType().getElementType()); getResult().setType(outputType); - } mlir::LogicalResult GetRangeOfVectorOp::verify() { - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); // auto inputType = llvm::dyn_cast(getOperand().getType()); // auto inputRank = inputType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // //once ensured only 1 rank from above -- + // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // //once ensured only 1 rank from above -- // if( inputRank != 1 ) // { // llvm::errs() << "inputRank: " << inputRank << "\n"; @@ -1803,31 +1831,32 @@ mlir::LogicalResult GetRangeOfVectorOp::verify() { // FIRFilterHammingOptimizedOp //===----------------------------------------------------------------------===// -void FIRFilterHammingOptimizedOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value wc, mlir::Value n) { - DEBUG_PRINT_NO_ARGS() ; +void FIRFilterHammingOptimizedOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, + mlir::Value wc, mlir::Value n) { + DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); state.addOperands({wc, n}); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); } void FIRFilterHammingOptimizedOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - // auto inputType = llvm::dyn_cast(getN().getType()); + // for each rank + // Get the shape/size of input + // output size = input_size + // auto inputType = llvm::dyn_cast(getN().getType()); // auto shapeOfInput = inputType.getShape(); - std::vector shapeForOutput ; + std::vector shapeForOutput; uint64_t GetLen = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int DEBUG_PRINT_NO_ARGS(); Value inputLen = getOperand(1); dsp::ConstantOp constantOp1stArg = inputLen.getDefiningOp(); @@ -1835,30 +1864,27 @@ void FIRFilterHammingOptimizedOp::inferShapes() { DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); auto elements = constantLhsValue.getValues(); float LenN = elements[0].getValueAsDouble(); - GetLen = (uint64_t) LenN; + GetLen = (uint64_t)LenN; DEBUG_PRINT_WITH_ARGS(GetLen); - DEBUG_PRINT_WITH_ARGS("GetLen= " , GetLen); - - //int64_t N = tensorType.getShape()[0]; + DEBUG_PRINT_WITH_ARGS("GetLen= ", GetLen); + // int64_t N = tensorType.getShape()[0]; shapeForOutput.push_back(GetLen); - mlir::TensorType outputType = mlir::RankedTensorType::get(shapeForOutput, - getWc().getType().getElementType()); - + mlir::TensorType outputType = mlir::RankedTensorType::get( + shapeForOutput, getWc().getType().getElementType()); getResult().setType(outputType); - } mlir::LogicalResult FIRFilterHammingOptimizedOp::verify() { uint64_t GetLen = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int DEBUG_PRINT_NO_ARGS(); Value inputLen = getOperand(1); dsp::ConstantOp constantOp1stArg = inputLen.getDefiningOp(); @@ -1866,17 +1892,16 @@ mlir::LogicalResult FIRFilterHammingOptimizedOp::verify() { DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); auto elements = constantLhsValue.getValues(); float LenN = elements[0].getValueAsDouble(); - GetLen = (uint64_t) LenN; + GetLen = (uint64_t)LenN; DEBUG_PRINT_WITH_ARGS(GetLen); - DEBUG_PRINT_WITH_ARGS("GetLen= " , GetLen); - - //filter-order even not supported -- so making it odd - if(GetLen % 2 == 0 ) - { + DEBUG_PRINT_WITH_ARGS("GetLen= ", GetLen); + + // filter-order even not supported -- so making it odd + if (GetLen % 2 == 0) { // GetLen = GetLen + 1; llvm::errs() << "N for lowPassFilter must be odd but is " << GetLen << "\n"; - // DEBUG_PRINT_WITH_ARGS("Making LowPassFilterLen Odd= " , GetLen); - return mlir::failure(); + // DEBUG_PRINT_WITH_ARGS("Making LowPassFilterLen Odd= " , GetLen); + return mlir::failure(); } return mlir::success(); } @@ -1885,31 +1910,32 @@ mlir::LogicalResult FIRFilterHammingOptimizedOp::verify() { // HighPassFIRHammingOptimizedOp //===----------------------------------------------------------------------===// -void HighPassFIRHammingOptimizedOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value wc, mlir::Value n) { - DEBUG_PRINT_NO_ARGS() ; +void HighPassFIRHammingOptimizedOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, + mlir::Value wc, mlir::Value n) { + DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); state.addOperands({wc, n}); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); } void HighPassFIRHammingOptimizedOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - // auto inputType = llvm::dyn_cast(getN().getType()); + // for each rank + // Get the shape/size of input + // output size = input_size + // auto inputType = llvm::dyn_cast(getN().getType()); // auto shapeOfInput = inputType.getShape(); - std::vector shapeForOutput ; + std::vector shapeForOutput; uint64_t GetLen = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int DEBUG_PRINT_NO_ARGS(); Value inputLen = getOperand(1); dsp::ConstantOp constantOp1stArg = inputLen.getDefiningOp(); @@ -1917,30 +1943,27 @@ void HighPassFIRHammingOptimizedOp::inferShapes() { DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); auto elements = constantLhsValue.getValues(); float LenN = elements[0].getValueAsDouble(); - GetLen = (uint64_t) LenN; + GetLen = (uint64_t)LenN; DEBUG_PRINT_WITH_ARGS(GetLen); - DEBUG_PRINT_WITH_ARGS("GetLen= " , GetLen); - - //int64_t N = tensorType.getShape()[0]; + DEBUG_PRINT_WITH_ARGS("GetLen= ", GetLen); + // int64_t N = tensorType.getShape()[0]; shapeForOutput.push_back(GetLen); - mlir::TensorType outputType = mlir::RankedTensorType::get(shapeForOutput, - getWc().getType().getElementType()); - + mlir::TensorType outputType = mlir::RankedTensorType::get( + shapeForOutput, getWc().getType().getElementType()); getResult().setType(outputType); - } mlir::LogicalResult HighPassFIRHammingOptimizedOp::verify() { uint64_t GetLen = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int DEBUG_PRINT_NO_ARGS(); Value inputLen = getOperand(1); dsp::ConstantOp constantOp1stArg = inputLen.getDefiningOp(); @@ -1948,22 +1971,20 @@ mlir::LogicalResult HighPassFIRHammingOptimizedOp::verify() { DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); auto elements = constantLhsValue.getValues(); float LenN = elements[0].getValueAsDouble(); - GetLen = (uint64_t) LenN; + GetLen = (uint64_t)LenN; DEBUG_PRINT_WITH_ARGS(GetLen); - DEBUG_PRINT_WITH_ARGS("GetLen= " , GetLen); - - //filter-order even not supported -- so making it odd - if(GetLen % 2 == 0 ) - { + DEBUG_PRINT_WITH_ARGS("GetLen= ", GetLen); + + // filter-order even not supported -- so making it odd + if (GetLen % 2 == 0) { // GetLen = GetLen + 1; llvm::errs() << "N for lowPassFilter must be odd but is " << GetLen << "\n"; - // DEBUG_PRINT_WITH_ARGS("Making LowPassFilterLen Odd= " , GetLen); - return mlir::failure(); + // DEBUG_PRINT_WITH_ARGS("Making LowPassFilterLen Odd= " , GetLen); + return mlir::failure(); } return mlir::success(); } - //===----------------------------------------------------------------------===// // ThresholdOp //===----------------------------------------------------------------------===// @@ -1972,23 +1993,23 @@ void ThresholdOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, mlir::Value input, mlir::Value threshld) { DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); - state.addOperands({input, threshld} ); + state.addOperands({input, threshld}); DEBUG_PRINT_NO_ARGS(); } void ThresholdOp::inferShapes() { DEBUG_PRINT_NO_ARGS(); - auto tensorInput = getInput().getType(); + auto tensorInput = getInput().getType(); getResult().setType(tensorInput); DEBUG_PRINT_NO_ARGS(); } mlir::LogicalResult ThresholdOp::verify() { - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int DEBUG_PRINT_NO_ARGS(); Value threshold = getOperand(1); dsp::ConstantOp constantOp1stArg = threshold.getDefiningOp(); @@ -1996,52 +2017,54 @@ mlir::LogicalResult ThresholdOp::verify() { DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); auto elements = constantLhsValue.getValues(); float GetThresholdVal = elements[0].getValueAsDouble(); - + DEBUG_PRINT_WITH_ARGS(GetThresholdVal); - DEBUG_PRINT_WITH_ARGS("GetThresholdVal= " , GetThresholdVal); - - //filter-order even not supported -- so making it odd - if(GetThresholdVal <= 0 ) - { + DEBUG_PRINT_WITH_ARGS("GetThresholdVal= ", GetThresholdVal); + + // filter-order even not supported -- so making it odd + if (GetThresholdVal <= 0) { // GetThresholdVal = GetThresholdVal + 1; - llvm::errs() << "threshold value must be >= 0 but got: " << GetThresholdVal << "\n"; - // DEBUG_PRINT_WITH_ARGS("Making LowPassFilterLen Odd= " , GetThresholdVal); - return mlir::failure(); + llvm::errs() << "threshold value must be >= 0 but got: " << GetThresholdVal + << "\n"; + // DEBUG_PRINT_WITH_ARGS("Making LowPassFilterLen Odd= " , GetThresholdVal); + return mlir::failure(); } return mlir::success(); - } //===----------------------------------------------------------------------===// // QuantizationOp //===----------------------------------------------------------------------===// -void QuantizationOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value input, mlir::Value nLevels, mlir::Value max, mlir::Value min) { +void QuantizationOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value input, + mlir::Value nLevels, mlir::Value max, + mlir::Value min) { DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); - state.addOperands({input, nLevels, max, min} ); + state.addOperands({input, nLevels, max, min}); DEBUG_PRINT_NO_ARGS(); } void QuantizationOp::inferShapes() { DEBUG_PRINT_NO_ARGS(); - auto tensorInput = getInput().getType(); + auto tensorInput = getInput().getType(); getResult().setType(tensorInput); DEBUG_PRINT_NO_ARGS(); } mlir::LogicalResult QuantizationOp::verify() { - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int // DEBUG_PRINT_NO_ARGS(); - // check max > min && NoOfLevels = powerOf2 + // check max > min && NoOfLevels = powerOf2 Value maxOperand = getOperand(2); - dsp::ConstantOp constantOp1stArg = maxOperand.getDefiningOp(); + dsp::ConstantOp constantOp1stArg = + maxOperand.getDefiningOp(); DEBUG_PRINT_NO_ARGS(); DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); auto elements = constantLhsValue.getValues(); @@ -2050,45 +2073,48 @@ mlir::LogicalResult QuantizationOp::verify() { Value minOperand = getOperand(3); constantOp1stArg = minOperand.getDefiningOp(); - if(!constantOp1stArg){ - llvm::errs() << "QuantizationOp: unable to get Constant for minOp -- 4th opernad " << "\n"; - return mlir::failure(); + if (!constantOp1stArg) { + llvm::errs() + << "QuantizationOp: unable to get Constant for minOp -- 4th opernad " + << "\n"; + return mlir::failure(); } DEBUG_PRINT_NO_ARGS(); constantLhsValue = constantOp1stArg.getValue(); elements = constantLhsValue.getValues(); float getMin = elements[0].getValueAsDouble(); - if(getMax < getMin){ - llvm::errs() << "QuantizatnOp : Max < Min --" << " Max: " << getMax ; - llvm::errs() << " Min: " << getMin ; + if (getMax < getMin) { + llvm::errs() << "QuantizatnOp : Max < Min --" << " Max: " << getMax; + llvm::errs() << " Min: " << getMin; return mlir::failure(); } - return mlir::success(); - } - //===----------------------------------------------------------------------===// // LMSFilterResponseOp //===----------------------------------------------------------------------===// -void LMSFilterResponseOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs, mlir::Value mu, mlir::Value filterLen) { - +void LMSFilterResponseOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value lhs, + mlir::Value rhs, mlir::Value mu, + mlir::Value filterLen) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); - state.addOperands({lhs, rhs, mu, filterLen}); + state.addOperands({lhs, rhs, mu, filterLen}); } - -void LMSFilterResponseOp::inferShapes() { getResult().setType(getLhs().getType()); } +void LMSFilterResponseOp::inferShapes() { + getResult().setType(getLhs().getType()); +} mlir::LogicalResult LMSFilterResponseOp::verify() { - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); // auto inputType = llvm::dyn_cast(getOperand(0).getType()); - // auto filterType = llvm::dyn_cast(getOperand(1).getType()); + // auto filterType = + // llvm::dyn_cast(getOperand(1).getType()); // // auto resultType = llvm::dyn_cast(getType()); // auto inputRank = inputType.getRank(); @@ -2107,90 +2133,89 @@ mlir::LogicalResult LMSFilterResponseOp::verify() { // RunLenEncodingOp //===----------------------------------------------------------------------===// -void RunLenEncodingOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value input) { +void RunLenEncodingOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value input) { DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); - state.addOperands({input} ); + state.addOperands({input}); DEBUG_PRINT_NO_ARGS(); } void RunLenEncodingOp::inferShapes() { DEBUG_PRINT_NO_ARGS(); - auto tensorInput = getInput().getType(); + auto tensorInput = getInput().getType(); auto shapeOfInput = tensorInput.getShape(); - // auto tensorUpsampling = getRhs().getType(); + // auto tensorUpsampling = getRhs().getType(); // auto shapeOfUpsampling = tensorUpsampling.getShape(); //shape is the length - //Assume rank is 1 , then get the shape of output + // Assume rank is 1 , then get the shape of output // shapeOfInput - std::vector shapeForOutput ; + std::vector shapeForOutput; int64_t LengthOfInput = shapeOfInput[0]; int64_t lenOfOutput = 2 * LengthOfInput; shapeForOutput.push_back(lenOfOutput); - - mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - getInput().getType().getElementType()); + + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getInput().getType().getElementType()); getResult().setType(manipulatedType); DEBUG_PRINT_NO_ARGS(); } mlir::LogicalResult RunLenEncodingOp::verify() { - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int // DEBUG_PRINT_NO_ARGS(); - - return mlir::success(); + return mlir::success(); } //===----------------------------------------------------------------------===// // FIRFilterResSymmOptimizedOp //===----------------------------------------------------------------------===// -void FIRFilterResSymmOptimizedOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs) { +void FIRFilterResSymmOptimizedOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, + mlir::Value lhs, mlir::Value rhs) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands({lhs, rhs}); } - - -/// Infer the output shape of the FIRFilterResSymmOptimizedOp, this is required by the shape inference -/// interface. -//ToDo -- shape should be the length of Lhs + Rhs - 1 -void FIRFilterResSymmOptimizedOp::inferShapes() { - //get the shape of Lhs & rhs - //add the shape for each dimension - // auto tensorInput = llvm::cast(getLhs().getType()); - auto tensorInput = getLhs().getType(); +/// Infer the output shape of the FIRFilterResSymmOptimizedOp, this is required +/// by the shape inference interface. +// ToDo -- shape should be the length of Lhs + Rhs - 1 +void FIRFilterResSymmOptimizedOp::inferShapes() { + // get the shape of Lhs & rhs + // add the shape for each dimension + // auto tensorInput = llvm::cast(getLhs().getType()); + auto tensorInput = getLhs().getType(); auto shapeOfInput = tensorInput.getShape(); auto tensorFilter = getRhs().getType(); auto shapeOfFilter = tensorFilter.getShape(); - std::vector shapeForOutput ; + std::vector shapeForOutput; - for(size_t i=0; i < shapeOfInput.size() ; i++){ + for (size_t i = 0; i < shapeOfInput.size(); i++) { shapeForOutput.push_back(shapeOfInput[i] + shapeOfFilter[i] - 1); } - - mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - getLhs().getType().getElementType()); - // getResult().setType(getLhs().getType()); + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getLhs().getType().getElementType()); + + // getResult().setType(getLhs().getType()); getResult().setType(manipulatedType); } -//get rank of Input & Filter -- make sure it is of rank 1 +// get rank of Input & Filter -- make sure it is of rank 1 mlir::LogicalResult FIRFilterResSymmOptimizedOp::verify() { // auto inputType = llvm::dyn_cast(getOperand(0).getType()); - // auto filterType = llvm::dyn_cast(getOperand(1).getType()); + // auto filterType = + // llvm::dyn_cast(getOperand(1).getType()); // // auto resultType = llvm::dyn_cast(getType()); // auto inputRank = inputType.getRank(); @@ -2205,16 +2230,15 @@ mlir::LogicalResult FIRFilterResSymmOptimizedOp::verify() { return mlir::success(); } - //===----------------------------------------------------------------------===// // LengthOp //===----------------------------------------------------------------------===// void LengthOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value input) { + mlir::Value input) { DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); - state.addOperands({input} ); + state.addOperands({input}); DEBUG_PRINT_NO_ARGS(); } @@ -2225,8 +2249,8 @@ void LengthOp::inferShapes() { DEBUG_PRINT_NO_ARGS(); shapeForOutput.push_back(1); - mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - getInput().getType().getElementType()); + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getInput().getType().getElementType()); getResult().setType(manipulatedType); DEBUG_PRINT_NO_ARGS(); } @@ -2250,16 +2274,17 @@ mlir::LogicalResult LengthOp::verify() { // ReverseInputOp //===----------------------------------------------------------------------===// -void ReverseInputOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value input) { +void ReverseInputOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value input) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands(input); } void ReverseInputOp::inferShapes() { - auto tensorInput = getInput().getType(); - // mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - // getInput().getType().getElementType()); + auto tensorInput = getInput().getType(); + // mlir::TensorType manipulatedType = + // mlir::RankedTensorType::get(shapeForOutput, + // getInput().getType().getElementType()); getResult().setType(tensorInput); } @@ -2278,129 +2303,131 @@ mlir::LogicalResult ReverseInputOp::verify() { return mlir::success(); } - //===----------------------------------------------------------------------===// // PaddingOp //===----------------------------------------------------------------------===// void PaddingOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value input, mlir::Value PadValue, mlir::Value PadLen) { + mlir::Value input, mlir::Value PadValue, + mlir::Value PadLen) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands({input, PadValue, PadLen}); } - - -/// Infer the output shape of the PaddingOp, this is required by the shape inference -/// interface. -//ToDo -- shape should be the length of input * UpsamplingRate ie, Rhs -void PaddingOp::inferShapes() { - //get the shape of Lhs & rhs - //add the shape for each dimension - // auto tensorInput = llvm::cast(getLhs().getType()); - auto tensorInput = getInput().getType(); +/// Infer the output shape of the PaddingOp, this is required by the shape +/// inference interface. +// ToDo -- shape should be the length of input * UpsamplingRate ie, Rhs +void PaddingOp::inferShapes() { + // get the shape of Lhs & rhs + // add the shape for each dimension + // auto tensorInput = llvm::cast(getLhs().getType()); + auto tensorInput = getInput().getType(); auto shapeOfInput = tensorInput.getShape(); - // auto tensorUpsampling = getRhs().getType(); + // auto tensorUpsampling = getRhs().getType(); // auto shapeOfUpsampling = tensorUpsampling.getShape(); //shape is the length - - std::vector shapeForOutput ; + std::vector shapeForOutput; int64_t SecondValueInt = 1; - //To extract value from the SSA value: - //get the Operand - //convert it to ConstantOp - //convert it to corresponding elements attribute - //extract the value as float then convert to int + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int DEBUG_PRINT_NO_ARGS(); Value padding3rdArg = getOperand(2); - dsp::ConstantOp constantOp2ndArg = padding3rdArg.getDefiningOp(); + dsp::ConstantOp constantOp2ndArg = + padding3rdArg.getDefiningOp(); DEBUG_PRINT_NO_ARGS(); - DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue();; + DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue(); + ; auto elements = constantRhsValue.getValues(); float SecondValue = elements[0].getValueAsDouble(); - SecondValueInt = (int64_t) SecondValue; - // llvm::errs() << "Upsampling: SamplingRate: " << SecondValueInt << " \n"; //downsamplingRate - - DEBUG_PRINT_NO_ARGS(); - for(size_t i=0; i < shapeOfInput.size() ; i++){ - double GetLenForOutput = static_cast(shapeOfInput[i] ) + SecondValueInt ; - int64_t OutlenInt = static_cast (GetLenForOutput); - DEBUG_PRINT_WITH_ARGS("PaddingLen= " , OutlenInt); + SecondValueInt = (int64_t)SecondValue; + // llvm::errs() << "Upsampling: SamplingRate: " << SecondValueInt << " \n"; + // //downsamplingRate + + DEBUG_PRINT_NO_ARGS(); + for (size_t i = 0; i < shapeOfInput.size(); i++) { + double GetLenForOutput = + static_cast(shapeOfInput[i]) + SecondValueInt; + int64_t OutlenInt = static_cast(GetLenForOutput); + DEBUG_PRINT_WITH_ARGS("PaddingLen= ", OutlenInt); shapeForOutput.push_back(OutlenInt); } - - mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - getInput().getType().getElementType()); - // getResult().setType(getLhs().getType()); + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getInput().getType().getElementType()); + + // getResult().setType(getLhs().getType()); getResult().setType(manipulatedType); - } +} -//get rank of Input & Upsampling -- make sure it is of rank 1 +// get rank of Input & Upsampling -- make sure it is of rank 1 mlir::LogicalResult PaddingOp::verify() { // auto inputType = llvm::dyn_cast(getOperand(0).getType()); - // auto samplingRateType = llvm::dyn_cast(getOperand(1).getType()); + // auto samplingRateType = + // llvm::dyn_cast(getOperand(1).getType()); // // auto resultType = llvm::dyn_cast(getType()); // auto inputRank = inputType.getRank(); // auto samplingRateRank = samplingRateType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " samplingRateRank: " << samplingRateRank << "\n"; - // //once ensured only 1 rank from above -- also make sure there is just 1 elem - // if( inputRank != 1 || samplingRateRank != 0 ) + // // llvm::errs() << "inputRank: " << inputRank << " samplingRateRank: " << + // samplingRateRank << "\n"; + // //once ensured only 1 rank from above -- also make sure there is just 1 + // elem if( inputRank != 1 || samplingRateRank != 0 ) // { - // llvm::errs() << "inputRank: " << inputRank << " samplingRateRank: " << samplingRateRank << "\n"; - // return emitError() + // llvm::errs() << "inputRank: " << inputRank << " samplingRateRank: " << + // samplingRateRank << "\n"; return emitError() // << "expected rank of input is 1 & Upsampling is 0"; // } return mlir::success(); -} - +} //===----------------------------------------------------------------------===// // FIRFilterYSymmOptimizedOp //===----------------------------------------------------------------------===// -void FIRFilterYSymmOptimizedOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs) { +void FIRFilterYSymmOptimizedOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, + mlir::Value lhs, mlir::Value rhs) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands({lhs, rhs}); } - - -/// Infer the output shape of the FIRFilterYSymmOptimizedOp, this is required by the shape inference -/// interface. -//ToDo -- shape should be the length of Lhs + Rhs - 1 -void FIRFilterYSymmOptimizedOp::inferShapes() { - //get the shape of Lhs & rhs - //add the shape for each dimension - // auto tensorInput = llvm::cast(getLhs().getType()); - auto tensorInput = getLhs().getType(); +/// Infer the output shape of the FIRFilterYSymmOptimizedOp, this is required by +/// the shape inference interface. +// ToDo -- shape should be the length of Lhs + Rhs - 1 +void FIRFilterYSymmOptimizedOp::inferShapes() { + // get the shape of Lhs & rhs + // add the shape for each dimension + // auto tensorInput = llvm::cast(getLhs().getType()); + auto tensorInput = getLhs().getType(); auto shapeOfInput = tensorInput.getShape(); auto tensorFilter = getRhs().getType(); auto shapeOfFilter = tensorFilter.getShape(); - std::vector shapeForOutput ; + std::vector shapeForOutput; - for(size_t i=0; i < shapeOfInput.size() ; i++){ + for (size_t i = 0; i < shapeOfInput.size(); i++) { shapeForOutput.push_back(shapeOfInput[i] + shapeOfFilter[i] - 1); } - - mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - getLhs().getType().getElementType()); - // getResult().setType(getLhs().getType()); + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getLhs().getType().getElementType()); + + // getResult().setType(getLhs().getType()); getResult().setType(manipulatedType); } -//get rank of Input & Filter -- make sure it is of rank 1 +// get rank of Input & Filter -- make sure it is of rank 1 mlir::LogicalResult FIRFilterYSymmOptimizedOp::verify() { // auto inputType = llvm::dyn_cast(getOperand(0).getType()); - // auto filterType = llvm::dyn_cast(getOperand(1).getType()); + // auto filterType = + // llvm::dyn_cast(getOperand(1).getType()); // // auto resultType = llvm::dyn_cast(getType()); // auto inputRank = inputType.getRank(); @@ -2419,19 +2446,19 @@ mlir::LogicalResult FIRFilterYSymmOptimizedOp::verify() { // FFT1DRealSymmOp //===----------------------------------------------------------------------===// -void FFT1DRealSymmOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value value) { - DEBUG_PRINT_NO_ARGS() ; +void FFT1DRealSymmOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value value) { + DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); state.addOperands(value); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); } void FFT1DRealSymmOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - auto tensorInput = getInput().getType(); + // for each rank + // Get the shape/size of input + // output size = input_size + auto tensorInput = getInput().getType(); // getResult().setType(tensorInput); getResult().setType(tensorInput); // getResult(2).setType(tensorInput); @@ -2442,8 +2469,9 @@ mlir::LogicalResult FFT1DRealSymmOp::verify() { // auto inputType = llvm::dyn_cast(getOperand().getType()); // auto inputRank = inputType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // //once ensured only 1 rank from above -- + // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // //once ensured only 1 rank from above -- // if( inputRank != 1 ) // { // llvm::errs() << "inputRank: " << inputRank << "\n"; @@ -2457,31 +2485,32 @@ mlir::LogicalResult FFT1DRealSymmOp::verify() { // FFT1DImgConjSymmOp //===----------------------------------------------------------------------===// -void FFT1DImgConjSymmOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value value) { - DEBUG_PRINT_NO_ARGS() ; +void FFT1DImgConjSymmOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value value) { + DEBUG_PRINT_NO_ARGS(); state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); state.addOperands(value); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); } void FFT1DImgConjSymmOp::inferShapes() { - //for each rank - //Get the shape/size of input - //output size = input_size - auto tensorInput = getInput().getType(); + // for each rank + // Get the shape/size of input + // output size = input_size + auto tensorInput = getInput().getType(); // getResult().setType(tensorInput); getResult().setType(tensorInput); // getResult(2).setType(tensorInput); } mlir::LogicalResult FFT1DImgConjSymmOp::verify() { - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); // auto inputType = llvm::dyn_cast(getOperand().getType()); // auto inputRank = inputType.getRank(); - // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << alphaValueRank << "\n"; - // //once ensured only 1 rank from above -- + // // llvm::errs() << "inputRank: " << inputRank << " alphaValueRank: " << + // alphaValueRank << "\n"; + // //once ensured only 1 rank from above -- // if( inputRank != 1 ) // { // llvm::errs() << "inputRank: " << inputRank << "\n"; @@ -2491,7 +2520,6 @@ mlir::LogicalResult FFT1DImgConjSymmOp::verify() { return mlir::success(); } - //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 491c23565367..8c16e1d5b58d 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#pragma GCC diagnostic push +#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wall" #include "mlir/IR/BuiltinAttributes.h" @@ -26,15 +26,15 @@ #include "mlir/Support/LLVM.h" #include "mlir/Support/LogicalResult.h" #include "mlir/Support/TypeID.h" -#include "toy/Dialect.h" #include "toy/DebugConfig.h" +#include "toy/Dialect.h" #include "toy/Passes.h" #include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/Func/IR/FuncOps.h" -#include "mlir/Dialect/MemRef/IR/MemRef.h" #include "mlir/Dialect/Math/IR/Math.h" +#include "mlir/Dialect/MemRef/IR/MemRef.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/DialectConversion.h" #include "llvm/ADT/ArrayRef.h" @@ -47,9 +47,9 @@ #include #include -//For IntegerSet -#include "mlir/IR/IntegerSet.h" +// For IntegerSet #include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/IR/IntegerSet.h" #include using namespace mlir; using namespace std; @@ -59,7 +59,6 @@ using namespace dsp; // ToyToAffine RewritePatterns //===----------------------------------------------------------------------===// - // #pragma warning(push, 0) /// Convert the given RankedTensorType into the corresponding MemRefType. static MemRefType convertTensorToMemRef(RankedTensorType type) { @@ -73,12 +72,15 @@ static Value insertAllocAndDealloc(MemRefType type, Location loc, // Make sure to allocate at the beginning of the block. auto *parentBlock = alloc->getBlock(); - alloc->moveBefore(&parentBlock->front()); //Abhinav-- move allock->block->front before alloc operation?? + alloc->moveBefore( + &parentBlock->front()); // Abhinav-- move allock->block->front before + // alloc operation?? // Make sure to deallocate this alloc at the end of the block. This is fine // as dsp functions have no control flow. auto dealloc = rewriter.create(loc, alloc); - dealloc->moveBefore(&parentBlock->back()); //move alloc->block->back before dealloc + dealloc->moveBefore( + &parentBlock->back()); // move alloc->block->back before dealloc return alloc; } @@ -94,29 +96,32 @@ static void lowerOpToLoops(Operation *op, ValueRange operands, PatternRewriter &rewriter, LoopIterationFn processIteration) { auto tensorType = llvm::cast((*op->result_type_begin())); - - // for (auto i : tensorType.getShape()) - // { - // llvm::errs() << "tensortype =" << i << "\n" ; - // } - // llvm::errs() << "tensortype.getElementType =" << tensorType.getElementType() << "\n" ; - // llvm::errs() << "op->getLoc = " << op->getLoc() << "\n"; //getDialect - // llvm::errs() << "op->getDialect = " << op->getDialect() << "\n"; - // llvm::errs() << "op->getName = " << op->getName() << "\n"; - // // llvm::errs() << "op->getType = " << op->getType() << "\n"; - // llvm::errs() << "op->getParentRegion = " << op->getParentRegion() << "\n"; - // llvm::errs() << "op->getParentOp = " << op->getParentOp()->getName() << "\n"; - - // llvm::errs() << "op->getNumOperands = " << op->getNumOperands() << "\n"; - // for (auto i : op->getOperands()) - // { - // llvm::errs() << "op->Operand = " << i << "\n"; - // } - - // llvm::errs() << "op->getParentOp = " << op->getParentOp()->getName() << "\n"; - // llvm::errs() << "op->getParentOp = " << op->getParentOp()->getName() << "\n"; - // llvm::errs() << "op->getParentOp = " << op->getParentOp()->getName() << "\n"; - + + // for (auto i : tensorType.getShape()) + // { + // llvm::errs() << "tensortype =" << i << "\n" ; + // } + // llvm::errs() << "tensortype.getElementType =" << + // tensorType.getElementType() << "\n" ; llvm::errs() << "op->getLoc = " << + // op->getLoc() << "\n"; //getDialect llvm::errs() << "op->getDialect = " << + // op->getDialect() << "\n"; llvm::errs() << "op->getName = " << op->getName() + // << "\n"; + // // llvm::errs() << "op->getType = " << op->getType() << "\n"; + // llvm::errs() << "op->getParentRegion = " << op->getParentRegion() << "\n"; + // llvm::errs() << "op->getParentOp = " << op->getParentOp()->getName() << + // "\n"; + + // llvm::errs() << "op->getNumOperands = " << op->getNumOperands() << "\n"; + // for (auto i : op->getOperands()) + // { + // llvm::errs() << "op->Operand = " << i << "\n"; + // } + + // llvm::errs() << "op->getParentOp = " << op->getParentOp()->getName() << + // "\n"; llvm::errs() << "op->getParentOp = " << op->getParentOp()->getName() + // << "\n"; llvm::errs() << "op->getParentOp = " << + // op->getParentOp()->getName() << "\n"; + auto loc = op->getLoc(); // Insert an allocation and deallocation for the result of this operation. @@ -156,22 +161,21 @@ static void lowerOpToLoops(Operation *op, ValueRange operands, rewriter.replaceOp(op, alloc); } - -#define TryJustAffineLoop 0 //working -#define TryAffineForAndAffineIf 0 // working +#define TryJustAffineLoop 0 // working +#define TryAffineForAndAffineIf 0 // working #define TryAffineIf2 0 -#define TryAffineMap 0 //working basic -- TO do --try with symbols -#define TrySumOfVector 0 //Working -#define TryMultiDimLoop 0 //Working -#define TryFIRFilter 1 -#define TryMultiDimForAndIf 0 // -#define TryMultiDimLoopAndAffineMap 0 //Working -#define TryMultiDimLoopAndAffineSet 0 //Working +#define TryAffineMap 0 // working basic -- TO do --try with symbols +#define TrySumOfVector 0 // Working +#define TryMultiDimLoop 0 // Working +#define TryFIRFilter 1 +#define TryMultiDimForAndIf 0 // +#define TryMultiDimLoopAndAffineMap 0 // Working +#define TryMultiDimLoopAndAffineSet 0 // Working static void lowerOpToLoopsFIR(Operation *op, ValueRange operands, - PatternRewriter &rewriter, - LoopIterationFn processIteration) { + PatternRewriter &rewriter, + LoopIterationFn processIteration) { auto tensorType = llvm::cast((*op->result_type_begin())); - + auto loc = op->getLoc(); // Insert an allocation and deallocation for the result of this operation. @@ -186,798 +190,834 @@ static void lowerOpToLoopsFIR(Operation *op, ValueRange operands, SmallVector steps(tensorType.getRank(), /*Value=*/1); // llvm::errs() << "tensorType.getRank() " << tensorType.getRank() << "\n"; - // cout << "tensorType.getRank() .. " << tensorType.getRank() << "\n"; - // for (auto i : tensorType.getRank()) - // { - // llvm::errs() << "tensorType.getRank() = " << i << "\n"; - // } - // for (auto i : tensorType.getShape()) - // { - // llvm::errs() << "tensorType.getShape() = " << i << "\n"; - // } - // llvm::errs() << "tensorType.getShape() " << tensorType.getShape() << "\n"; + // cout << "tensorType.getRank() .. " << tensorType.getRank() << "\n"; + // for (auto i : tensorType.getRank()) + // { + // llvm::errs() << "tensorType.getRank() = " << i << "\n"; + // } + // for (auto i : tensorType.getShape()) + // { + // llvm::errs() << "tensorType.getShape() = " << i << "\n"; + // } + // llvm::errs() << "tensorType.getShape() " << tensorType.getShape() << "\n"; - // affine::AffineForOp forOp = rewriter.create( - // loc, lowerBounds, tensorType.getShape() , steps, ValueRange()); - // mlir::IntegerSet set1 = mlir::IntegerSet::get(1, 0, map, {true}); + // affine::AffineForOp forOp = rewriter.create( + // loc, lowerBounds, tensorType.getShape() , steps, ValueRange()); + // mlir::IntegerSet set1 = mlir::IntegerSet::get(1, 0, map, {true}); - //create an affineFor - // affineFor It has one region containing its body & the region must contain a block terminating with affine.yield - //block has argument of index type - // + // create an affineFor + // affineFor It has one region containing its body & the region must contain + // a block terminating with affine.yield + // block has argument of index type + // #if TryJustAffineLoop - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0]; - int64_t step = 1; + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + // create AffineMap and set + // %1 = affine.load + // if ( %arg0 >= 5) ie, integerSet <(d0) : (d0 - 5 >= 0) > + AffineExpr dimExpr = + rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(5); + // AffineMap map = AffineMap::get(1, 0, dimExpr); + // AffineMap map = AffineMap::get(1, 0 , rewriter.getAffineDimExpr(0) - 5); + IntegerSet set1 = IntegerSet::get(1, 0, {dimExpr}, {false}); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); + + // inside the forOp body --> create the operations & then close the body + // OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPointToStart(forOp1.getBody()); + + // start adding operations like a arith::constant = 100.0 to the body of + // forOp1 + // Inside the loop body: + + Value constant15 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(15)); + + llvm::errs() << "LINE = " << __LINE__ << "\n"; + auto storeOp = rewriter.create( + loc, constant15, alloc, forOp1.getInductionVar()); - //create AffineMap and set - // %1 = affine.load - // if ( %arg0 >= 5) ie, integerSet <(d0) : (d0 - 5 >= 0) > - AffineExpr dimExpr = rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(5); - // AffineMap map = AffineMap::get(1, 0, dimExpr); - // AffineMap map = AffineMap::get(1, 0 , rewriter.getAffineDimExpr(0) - 5); - IntegerSet set1 = IntegerSet::get(1, 0, {dimExpr}, {false}); - affine::AffineForOp forOp1 = rewriter.create(loc, - lb, ub, step ); - - //inside the forOp body --> create the operations & then close the body - // OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPointToStart(forOp1.getBody()); - - //start adding operations like a arith::constant = 100.0 to the body of forOp1 - // Inside the loop body: - - Value constant15 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(15)); - - llvm::errs() << "LINE = " << __LINE__ << "\n"; - auto storeOp = rewriter.create(loc, constant15, alloc, forOp1.getInductionVar()); - -#endif +#endif #if TryAffineForAndAffineIf - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0]; - int64_t step = 1; - - //create AffineMap and set - // %1 = affine.load - // if ( %arg0 >= 5) ie, integerSet <(d0) : (d0 - 5 >= 0) > - AffineExpr dimExpr = rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(5); - // AffineExpr dimExpr2 = rewriter - // AffineMap map = AffineMap::get(1, 0, dimExpr); - // AffineMap map = AffineMap::get(1, 0 , rewriter.getAffineDimExpr(0) - 5); - IntegerSet set1 = IntegerSet::get(1, 0, {dimExpr}, {false}); - - //affine.if %arg1 >= 0 and %5 <= %1 - 1 - // n-k >= 0 && n-k <= len -1 //n = %arg0 , k = %arg1 - // %arg0 >= 0 and %arg0 - %arg1 - %sym1 + 1 <= 0 - - affine::AffineForOp forOp1 = rewriter.create(loc, - lb, ub, step ); - - //inside the forOp body --> create the operations & then close the body - // OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPointToStart(forOp1.getBody()); - auto iv = forOp1.getInductionVar(); - //start adding operations like a arith::constant = 100.0 to the body of forOp1 - // Inside the loop body: - - // #set affine_set<(d0) : (d0 - 5 <= 0)> - // affine.for %arg0 = 0 to 10 { - // %3 = affine.if #set (%arg0) { - // %1 = arith.const 25 - // affine.yield %1 - // } - // else{ - // %2 = arith.const 15 - // affine.yield %2 - // } - // affine.store %3, alloc[%arg0] - // } - - // auto ifOp = rewriter.create( loc, set1 , ValueRange{iv} , false /*no else*/ ); - // auto ifOp = rewriter.create( loc, set1 , ValueRange{iv} , true /*no else*/ ); - - //use typeRange too: - Type floatType = rewriter.getF64Type(); - auto ifOp = rewriter.create( loc, TypeRange{ floatType },set1 , ValueRange{iv} , true /*no else*/ ); + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + // create AffineMap and set + // %1 = affine.load + // if ( %arg0 >= 5) ie, integerSet <(d0) : (d0 - 5 >= 0) > + AffineExpr dimExpr = + rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(5); + // AffineExpr dimExpr2 = rewriter + // AffineMap map = AffineMap::get(1, 0, dimExpr); + // AffineMap map = AffineMap::get(1, 0 , rewriter.getAffineDimExpr(0) - 5); + IntegerSet set1 = IntegerSet::get(1, 0, {dimExpr}, {false}); + + // affine.if %arg1 >= 0 and %5 <= %1 - 1 + // n-k >= 0 && n-k <= len -1 //n = %arg0 , k = %arg1 + // %arg0 >= 0 and %arg0 - %arg1 - %sym1 + 1 <= 0 + + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); + + // inside the forOp body --> create the operations & then close the body + // OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPointToStart(forOp1.getBody()); + auto iv = forOp1.getInductionVar(); + // start adding operations like a arith::constant = 100.0 to the body of + // forOp1 + // Inside the loop body: + + // #set affine_set<(d0) : (d0 - 5 <= 0)> + // affine.for %arg0 = 0 to 10 { + // %3 = affine.if #set (%arg0) { + // %1 = arith.const 25 + // affine.yield %1 + // } + // else{ + // %2 = arith.const 15 + // affine.yield %2 + // } + // affine.store %3, alloc[%arg0] + // } - rewriter.setInsertionPointToStart(ifOp.getThenBlock()); - - FIRFilterResponseAdaptor firFilterOperands(operands); - - //load from the input - Value loadInput = rewriter.create(loc, firFilterOperands.getLhs(), iv); - Value constant25 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(25)); - Value constsq25 = rewriter.create(loc, loadInput, constant25) ; - - rewriter.create(loc, constsq25 , alloc, iv); - rewriter.create(loc, ValueRange{constsq25}); - // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()); - - rewriter.setInsertionPointToStart(ifOp.getElseBlock()); - Value loadInput2 = rewriter.create(loc, firFilterOperands.getRhs(), iv); - Value constant15 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(15)); - Value elseResult = rewriter.create(loc, loadInput2, constant15) ; - rewriter.create(loc, elseResult , alloc, iv); - rewriter.create(loc, ValueRange{elseResult}); - // rewriter.setInsertionPointToEnd(ifOp.getElseBlock()); - rewriter.setInsertionPointAfter(ifOp); - ifOp->dump(); - // forOp1->dump(); - rewriter.create(loc, ifOp.getResult(0) , alloc, iv); - //getParentBlock then use - // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()->getParentOp()); - // rewriter.setInsertionPointToEnd(ifOp->getBlock()); - // rewriter.setInsertionPoint(ifOp->getParentOp()); - // rewriter.create(loc, ValueRange{constant25}); - // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()); - - // rewriter.setInsertionPointAfter(ifOp); - // rewriter.create(loc, ifOp.getResult(0) , alloc, iv); - - //try to add the affine.If condition - //create affine.If , - // use integer set to represent the condition - //check the AffineArgs - // affine.if operation contains two regions for the “then” and “else” clauses - //each region of affine.if must contain a single block with no args and terminated by affine.yield op - // if affine.if defines no values --> no need for affine.yield - - // affineIf.setConditional(set1, forOp1.getInductionVar()); - //start then "block" - // "then" block - - // Value constant15 = rewriter.create(loc, rewriter.getF64Type(), - // rewriter.getF64FloatAttr(15)); - - // rewriter.create(loc, ValueRange{constant15}); - // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()); - //else block - // rewriter.setInsertionPointToStart(ifOp.getElseBlock()); - - // Set insertion point to the end of the "then" block - // rewriter.setInsertionPointAfter(ifOp.getThenBlock()->getTerminator()); - - - // rewriter.create(loc, constant25); - llvm::errs() << "LINE = " << __LINE__ << "\n"; - //Back to parentOp -- ifOp stops here - // rewriter.setInsertionPointAfter(ifOp); - - - //also use affine::AffineStore to store at the loop induction variable - // auto storeOp = rewriter.create(loc, ifOp.getResult(0), alloc, forOp1.getInductionVar()); - // auto storeOp = rewriter.create(loc, constant25, alloc, forOp1.getInductionVar()); - // Back to parentOp -- forOp1 - // rewriter.setInsertionPointAfter(storeOp); - - llvm::errs() << "LINE = " << __LINE__ << " xx\n"; - //create affine yield for the loop - // rewriter.create(loc); + // auto ifOp = rewriter.create( loc, set1 , ValueRange{iv} + // , false /*no else*/ ); auto ifOp = rewriter.create( + // loc, set1 , ValueRange{iv} , true /*no else*/ ); + + // use typeRange too: + Type floatType = rewriter.getF64Type(); + auto ifOp = rewriter.create( + loc, TypeRange{floatType}, set1, ValueRange{iv}, true /*no else*/); + + rewriter.setInsertionPointToStart(ifOp.getThenBlock()); + + FIRFilterResponseAdaptor firFilterOperands(operands); + + // load from the input + Value loadInput = + rewriter.create(loc, firFilterOperands.getLhs(), iv); + Value constant25 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(25)); + Value constsq25 = rewriter.create(loc, loadInput, constant25); + + rewriter.create(loc, constsq25, alloc, iv); + rewriter.create(loc, ValueRange{constsq25}); + // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()); + + rewriter.setInsertionPointToStart(ifOp.getElseBlock()); + Value loadInput2 = + rewriter.create(loc, firFilterOperands.getRhs(), iv); + Value constant15 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(15)); + Value elseResult = + rewriter.create(loc, loadInput2, constant15); + rewriter.create(loc, elseResult, alloc, iv); + rewriter.create(loc, ValueRange{elseResult}); + // rewriter.setInsertionPointToEnd(ifOp.getElseBlock()); + rewriter.setInsertionPointAfter(ifOp); + ifOp->dump(); + // forOp1->dump(); + rewriter.create(loc, ifOp.getResult(0), alloc, iv); + // getParentBlock then use + // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()->getParentOp()); + // rewriter.setInsertionPointToEnd(ifOp->getBlock()); + // rewriter.setInsertionPoint(ifOp->getParentOp()); + // rewriter.create(loc, ValueRange{constant25}); + // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()); + + // rewriter.setInsertionPointAfter(ifOp); + // rewriter.create(loc, ifOp.getResult(0) , alloc, iv); + + // try to add the affine.If condition + // create affine.If , + // use integer set to represent the condition + // check the AffineArgs + // affine.if operation contains two regions for the “then” and “else” clauses + // each region of affine.if must contain a single block with no args and + // terminated by affine.yield op + // if affine.if defines no values --> no need for affine.yield + + // affineIf.setConditional(set1, forOp1.getInductionVar()); + // start then "block" + // "then" block + + // Value constant15 = rewriter.create(loc, + // rewriter.getF64Type(), + // rewriter.getF64FloatAttr(15)); + + // rewriter.create(loc, ValueRange{constant15}); + // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()); + // else block + // rewriter.setInsertionPointToStart(ifOp.getElseBlock()); + + // Set insertion point to the end of the "then" block + // rewriter.setInsertionPointAfter(ifOp.getThenBlock()->getTerminator()); + + // rewriter.create(loc, constant25); + llvm::errs() << "LINE = " << __LINE__ << "\n"; + // Back to parentOp -- ifOp stops here + // rewriter.setInsertionPointAfter(ifOp); + + // also use affine::AffineStore to store at the loop induction variable + // auto storeOp = rewriter.create(loc, + // ifOp.getResult(0), alloc, forOp1.getInductionVar()); auto storeOp = + // rewriter.create(loc, constant25, alloc, + // forOp1.getInductionVar()); Back to parentOp -- forOp1 + // rewriter.setInsertionPointAfter(storeOp); + + llvm::errs() << "LINE = " << __LINE__ << " xx\n"; + // create affine yield for the loop + // rewriter.create(loc); #endif #if TryAffineIf2 - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0]; - int64_t step = 1; - - //create AffineMap and set - // %1 = affine.load - // if ( %arg0 >= 5) ie, integerSet <(d0) : (d0 - 5 >= 0) > - AffineExpr dimExpr = rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(5); - // AffineExpr dimExpr2 = rewriter - // AffineMap map = AffineMap::get(1, 0, dimExpr); - // AffineMap map = AffineMap::get(1, 0 , rewriter.getAffineDimExpr(0) - 5); - IntegerSet set1 = IntegerSet::get(1, 0, {dimExpr}, {false}); - - //affine.if %arg1 >= 0 and %5 <= %1 - 1 - // n-k >= 0 && n-k <= len -1 //n = %arg0 , k = %arg1 - // %arg0 >= 0 and %arg0 - %arg1 - %sym1 + 1 <= 0 - - affine::AffineForOp forOp1 = rewriter.create(loc, - lb, ub, step ); + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + // create AffineMap and set + // %1 = affine.load + // if ( %arg0 >= 5) ie, integerSet <(d0) : (d0 - 5 >= 0) > + AffineExpr dimExpr = + rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(5); + // AffineExpr dimExpr2 = rewriter + // AffineMap map = AffineMap::get(1, 0, dimExpr); + // AffineMap map = AffineMap::get(1, 0 , rewriter.getAffineDimExpr(0) - 5); + IntegerSet set1 = IntegerSet::get(1, 0, {dimExpr}, {false}); + + // affine.if %arg1 >= 0 and %5 <= %1 - 1 + // n-k >= 0 && n-k <= len -1 //n = %arg0 , k = %arg1 + // %arg0 >= 0 and %arg0 - %arg1 - %sym1 + 1 <= 0 + + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); + + // inside the forOp body --> create the operations & then close the body + // OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPointToStart(forOp1.getBody()); + auto iv = forOp1.getInductionVar(); + // start adding operations like a arith::constant = 100.0 to the body of + // forOp1 + // Inside the loop body: + + // #set affine_set<(d0) : (d0 - 5 <= 0)> + // affine.for %arg0 = 0 to 10 { + // %3 = affine.if #set (%arg0) { + // %1 = arith.const 25 + // affine.yield %1 + // } + // affine.store %3, alloc[%arg0] + // } - //inside the forOp body --> create the operations & then close the body - // OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPointToStart(forOp1.getBody()); - auto iv = forOp1.getInductionVar(); - //start adding operations like a arith::constant = 100.0 to the body of forOp1 - // Inside the loop body: - - // #set affine_set<(d0) : (d0 - 5 <= 0)> - // affine.for %arg0 = 0 to 10 { - // %3 = affine.if #set (%arg0) { - // %1 = arith.const 25 - // affine.yield %1 - // } - // affine.store %3, alloc[%arg0] - // } + // auto ifOp = rewriter.create( loc, set1 , ValueRange{iv} + // , false /*no else*/ ); + auto ifOp = rewriter.create(loc, set1, ValueRange{iv}, + true /*no else*/); + rewriter.setInsertionPointToStart(ifOp.getThenBlock()); + // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()); + Value constant25 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(25)); + Value constsq25 = rewriter.create(loc, constant25, constant25); + + // ifOp.setR + // rewriter.create(loc, constant25 , alloc, iv); + // rewriter.setInsertionPointToStart(ifOp.getElseBlock()); + Value constant15 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(15)); + rewriter.create(loc, constsq25, alloc, iv); + + // getParentBlock then use + // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()->getParentOp()); + // rewriter.setInsertionPointToEnd(ifOp->getBlock()); + rewriter.setInsertionPoint(ifOp->getParentOp()); + // rewriter.create(loc, ValueRange{constant25}); + // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()); + + // rewriter.setInsertionPointAfter(ifOp); + // rewriter.create(loc, ifOp.getResult(0) , alloc, iv); + // rewriter.cre - // auto ifOp = rewriter.create( loc, set1 , ValueRange{iv} , false /*no else*/ ); - auto ifOp = rewriter.create( loc, set1 , ValueRange{iv} , true /*no else*/ ); - rewriter.setInsertionPointToStart(ifOp.getThenBlock()); - // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()); - Value constant25 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(25)); - Value constsq25 = rewriter.create(loc, constant25, constant25) ; - - // ifOp.setR - // rewriter.create(loc, constant25 , alloc, iv); - // rewriter.setInsertionPointToStart(ifOp.getElseBlock()); - Value constant15 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(15)); - rewriter.create(loc, constsq25 , alloc, iv); - - - //getParentBlock then use - // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()->getParentOp()); - // rewriter.setInsertionPointToEnd(ifOp->getBlock()); - rewriter.setInsertionPoint(ifOp->getParentOp()); - // rewriter.create(loc, ValueRange{constant25}); - // rewriter.setInsertionPointToEnd(ifOp.getThenBlock()); - - // rewriter.setInsertionPointAfter(ifOp); - // rewriter.create(loc, ifOp.getResult(0) , alloc, iv); - // rewriter.cre - #endif #if TryAffineMap - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0] - 2; - int64_t step = 1; - - affine::AffineForOp forOp1 = rewriter.create(loc, - lb, ub, step ); - - - //inside the forOp body --> create the operations & then close the body - // OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPointToStart(forOp1.getBody()); - auto iv = forOp1.getInductionVar(); - //start adding operations like a arith::constant = 100.0 to the body of forOp1 - // Inside the loop body: - //create affine for - // use affine-map expression for dimension then symbol then combination - // affine-map expression for dimension: affine_map (d0 , d1 + s0, d1 - s0) - // use affine map - // Define an affine map: #map2 = affine_map<(d0) -> (d0 + 2)> - auto symbol1 = tensorType.getShape()[0]; - AffineExpr indx = rewriter.getAffineDimExpr(0); - AffineExpr constantExpr = rewriter.getAffineConstantExpr(2); - AffineMap addMap = AffineMap::get(1, 0, symbol1 - indx); - auto outputIndex = rewriter.create(loc, addMap , iv); - - // Value constant15 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(15)); - - - //try replace constant15 ie, with input & filter - FIRFilterResponseOpAdaptor firOpAdaptor(operands); - - Value inputForFilter = rewriter.create(loc, firOpAdaptor.getLhs() , iv); - // Value inputForFilterMapped = rewriter.create(loc, firOpAdaptor.getLhs() , addMap, iv); - - Value impulseFilter = rewriter.create(loc, firOpAdaptor.getRhs() , iv); - - auto storeOp = rewriter.create(loc, inputForFilter, alloc,ValueRange{outputIndex}); - - - llvm::errs() << "LINE = " << __LINE__ << "\n"; + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0] - 2; + int64_t step = 1; + + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); + + // inside the forOp body --> create the operations & then close the body + // OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPointToStart(forOp1.getBody()); + auto iv = forOp1.getInductionVar(); + // start adding operations like a arith::constant = 100.0 to the body of + // forOp1 + // Inside the loop body: + // create affine for + // use affine-map expression for dimension then symbol then combination + // affine-map expression for dimension: affine_map (d0 , d1 + + // s0, d1 - s0) use affine map Define an affine map: #map2 = affine_map<(d0) + // -> (d0 + 2)> + auto symbol1 = tensorType.getShape()[0]; + AffineExpr indx = rewriter.getAffineDimExpr(0); + AffineExpr constantExpr = rewriter.getAffineConstantExpr(2); + AffineMap addMap = AffineMap::get(1, 0, symbol1 - indx); + auto outputIndex = rewriter.create(loc, addMap, iv); + + // Value constant15 = rewriter.create(loc, + // rewriter.getF64Type(), rewriter.getF64FloatAttr(15)); + + // try replace constant15 ie, with input & filter + FIRFilterResponseOpAdaptor firOpAdaptor(operands); + + Value inputForFilter = + rewriter.create(loc, firOpAdaptor.getLhs(), iv); + // Value inputForFilterMapped = rewriter.create(loc, + // firOpAdaptor.getLhs() , addMap, iv); + + Value impulseFilter = + rewriter.create(loc, firOpAdaptor.getRhs(), iv); + + auto storeOp = rewriter.create( + loc, inputForFilter, alloc, ValueRange{outputIndex}); + + llvm::errs() << "LINE = " << __LINE__ << "\n"; #endif #if TrySumOfVector - // here, we have to use iter - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0] ; - int64_t step = 1; - - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - - affine::AffineForOp forOp1 = rewriter.create(loc, - lb, ub, step , ValueRange{constant0} ); - - rewriter.setInsertionPointToStart(forOp1.getBody()); - auto iv = forOp1.getInductionVar(); - - - //inside the forOp body --> create the operations & then close the body - // OpBuilder::InsertionGuard guard(rewriter); - // Initial sum set to 0. - // %sum_0 = arith.constant 0.0 : f32 - // // iter_args binds initial values to the loop's region arguments. - // %sum = affine.for %i = 0 to 10 step 1 - // iter_args(%sum_iter = %sum_0) -> (f32) { - // %t = affine.load %buffer[%i] : memref<10xf32> - // %sum_next = arith.addf %sum_iter, %t : f32 - // // Yield current iteration sum to next iteration %sum_iter or to %sum - // // if final iteration. - // affine.yield %sum_next : f32 - // } - // return %sum : f32 - // } - - - // Inside the loop body: - - //try replace constant15 ie, with input & filter - FIRFilterResponseOpAdaptor firOpAdaptor(operands); - - Value inputForFilter = rewriter.create(loc, firOpAdaptor.getLhs() , iv); - - //Get iter_arg - auto getIterArg = forOp1.getBody()->getArgument(1); //forOp1.getIterOperands(); - Value sumNext = rewriter.create(loc, inputForFilter, getIterArg); - // Value sumNext = rewriter.create(loc, inputForFilter, constant0); - - //here, at indx 0 , o/p = in[0] - // at indx 1 , o/p = in[0] + in[1] & so on - //at indx last o/p[9] = sum of all input elements - auto storeOp = rewriter.create(loc, sumNext, alloc,ValueRange{iv}); - rewriter.create(loc, ValueRange{sumNext} ); - // rewriter.create(loc); - // auto result = forOp1.getResult(0); - llvm::errs() << "LINE = " << __LINE__ << "\n"; + // here, we have to use iter + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + + affine::AffineForOp forOp1 = rewriter.create( + loc, lb, ub, step, ValueRange{constant0}); + + rewriter.setInsertionPointToStart(forOp1.getBody()); + auto iv = forOp1.getInductionVar(); + + // inside the forOp body --> create the operations & then close the body + // OpBuilder::InsertionGuard guard(rewriter); + // Initial sum set to 0. + // %sum_0 = arith.constant 0.0 : f32 + // // iter_args binds initial values to the loop's region arguments. + // %sum = affine.for %i = 0 to 10 step 1 + // iter_args(%sum_iter = %sum_0) -> (f32) { + // %t = affine.load %buffer[%i] : memref<10xf32> + // %sum_next = arith.addf %sum_iter, %t : f32 + // // Yield current iteration sum to next iteration %sum_iter or to %sum + // // if final iteration. + // affine.yield %sum_next : f32 + // } + // return %sum : f32 + // } + + // Inside the loop body: + + // try replace constant15 ie, with input & filter + FIRFilterResponseOpAdaptor firOpAdaptor(operands); + + Value inputForFilter = + rewriter.create(loc, firOpAdaptor.getLhs(), iv); + + // Get iter_arg + auto getIterArg = + forOp1.getBody()->getArgument(1); // forOp1.getIterOperands(); + Value sumNext = + rewriter.create(loc, inputForFilter, getIterArg); + // Value sumNext = rewriter.create(loc, inputForFilter, + // constant0); + + // here, at indx 0 , o/p = in[0] + // at indx 1 , o/p = in[0] + in[1] & so on + // at indx last o/p[9] = sum of all input elements + auto storeOp = rewriter.create(loc, sumNext, alloc, + ValueRange{iv}); + rewriter.create(loc, ValueRange{sumNext}); + // rewriter.create(loc); + // auto result = forOp1.getResult(0); + llvm::errs() << "LINE = " << __LINE__ << "\n"; #endif #if TryMultiDimLoop - // here, we have to use iter - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0] ; - int64_t step = 1; - - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - - affine::AffineForOp forOp1 = rewriter.create(loc, - lb, ub, step ); - - rewriter.setInsertionPointToStart(forOp1.getBody()); - auto iv = forOp1.getInductionVar(); - - //create loadOp - FIRFilterResponseOpAdaptor firOpAdaptor(operands); - - Value loadInput = rewriter.create(loc, firOpAdaptor.getLhs() , iv); - - //create another loop -- - affine::AffineForOp forOp2 = rewriter.create(loc, - lb, ub, step , ValueRange{loadInput} ); - - rewriter.setInsertionPointToStart(forOp2.getBody()); - auto iv2 = forOp2.getInductionVar(); - Value loadFilter = rewriter.create(loc, firOpAdaptor.getRhs() , iv2); - - // get iterArg - auto getIterArg = forOp2.getBody()->getArgument(1); - auto sumNext = rewriter.create(loc, loadInput, loadFilter); - - - - //store the result to output - // rewriter.create(loc, sumNext, alloc, iv ); - rewriter.create(loc, ValueRange{sumNext}); - rewriter.setInsertionPointAfter(forOp2); - rewriter.create(loc, forOp2.getResult(0), alloc, iv ); - // - //yield the - //inside the forOp body --> create the operations & then close the body - // OpBuilder::InsertionGuard guard(rewriter); - // Initial sum set to 0. - // affine.for %arg0 = 0 to 10 { - // %1 = affine.load input[%arg0] - // %4 = affine.for %arg1 = 0 to 10 step 1 - // iter_args(%sum_iter = %1) { - // %2 = affine.load filter[%arg1] - // %3 = arith.add sum_iter , %2 - // affine.yield %3 : f64 - // } - // affine.store %4, output[%arg0] - // } - - - // Inside the loop body: + // here, we have to use iter + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); + + rewriter.setInsertionPointToStart(forOp1.getBody()); + auto iv = forOp1.getInductionVar(); + + // create loadOp + FIRFilterResponseOpAdaptor firOpAdaptor(operands); + + Value loadInput = + rewriter.create(loc, firOpAdaptor.getLhs(), iv); + + // create another loop -- + affine::AffineForOp forOp2 = rewriter.create( + loc, lb, ub, step, ValueRange{loadInput}); + + rewriter.setInsertionPointToStart(forOp2.getBody()); + auto iv2 = forOp2.getInductionVar(); + Value loadFilter = + rewriter.create(loc, firOpAdaptor.getRhs(), iv2); + + // get iterArg + auto getIterArg = forOp2.getBody()->getArgument(1); + auto sumNext = rewriter.create(loc, loadInput, loadFilter); + + // store the result to output + // rewriter.create(loc, sumNext, alloc, iv ); + rewriter.create(loc, ValueRange{sumNext}); + rewriter.setInsertionPointAfter(forOp2); + rewriter.create(loc, forOp2.getResult(0), alloc, iv); + // + // yield the + // inside the forOp body --> create the operations & then close the body + // OpBuilder::InsertionGuard guard(rewriter); + // Initial sum set to 0. + // affine.for %arg0 = 0 to 10 { + // %1 = affine.load input[%arg0] + // %4 = affine.for %arg1 = 0 to 10 step 1 + // iter_args(%sum_iter = %1) { + // %2 = affine.load filter[%arg1] + // %3 = arith.add sum_iter , %2 + // affine.yield %3 : f64 + // } + // affine.store %4, output[%arg0] + // } - - llvm::errs() << "LINE = " << __LINE__ << "\n"; + // Inside the loop body: + llvm::errs() << "LINE = " << __LINE__ << "\n"; #endif #if TryMultiDimForAndIf - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0]; - int64_t step = 1; - - //create AffineMap and set - // %1 = affine.load - // if ( %arg0 >= 5) ie, integerSet <(d0) : (d0 - 5 >= 0) > - - //affine.if %arg1 >= 0 and %5 <= %1 - 1 - // n-k >= 0 && n-k <= len -1 //n = %arg0 , k = %arg1 - // %arg0 >= 0 and %arg0 - %arg1 - %sym1 + 1 <= 0 - - affine::AffineForOp forOp1 = rewriter.create(loc, - lb, ub, step ); - - //inside the forOp body --> create the operations & then close the body - // OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPointToStart(forOp1.getBody()); - auto iv = forOp1.getInductionVar(); - //start adding operations like a arith::constant = 100.0 to the body of forOp1 - // Inside the loop body: - - AffineExpr dimExpr = rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(5); - IntegerSet set1 = IntegerSet::get(1, 0, {dimExpr}, {false}); + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + // create AffineMap and set + // %1 = affine.load + // if ( %arg0 >= 5) ie, integerSet <(d0) : (d0 - 5 >= 0) > + + // affine.if %arg1 >= 0 and %5 <= %1 - 1 + // n-k >= 0 && n-k <= len -1 //n = %arg0 , k = %arg1 + // %arg0 >= 0 and %arg0 - %arg1 - %sym1 + 1 <= 0 + + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); + + // inside the forOp body --> create the operations & then close the body + // OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPointToStart(forOp1.getBody()); + auto iv = forOp1.getInductionVar(); + // start adding operations like a arith::constant = 100.0 to the body of + // forOp1 + // Inside the loop body: + + AffineExpr dimExpr = + rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(5); + IntegerSet set1 = IntegerSet::get(1, 0, {dimExpr}, {false}); + + // create 2nd loop + // use loop inductn variable for 2nd loop + // use if condition on 2nd loop inductn variable + // get the result of inner for loop and store at output + + affine::AffineForOp forOp2 = + rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOp2.getBody()); + auto iv2 = forOp2.getInductionVar(); + AffineExpr dimExpr2 = + rewriter.getAffineDimExpr(1) - rewriter.getAffineConstantExpr(6); + IntegerSet set2 = IntegerSet::get(1, 0, {dimExpr, dimExpr2}, {false}); + + auto ifOp = rewriter.create(loc, set2, ValueRange{iv}, + false /*no else*/); + rewriter.setInsertionPointToStart(ifOp.getThenBlock()); + Value constant25 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(25)); + Value resultFromInnerLoop = + rewriter.create(loc, constant25, constant25); + + // rewriter.setInsertionPointAfter(forOp2); + // rewriter.setInsertionPointToEnd(forOp2->getBlock()); + // rewriter.create(loc, constant25 , alloc, iv2); + // rewriter.create(loc, ValueRange{resultFromInnerLoop}); + // rewriter.setInsertionPointAfter(ifOp); + // rewriter.create(loc, ValueRange{resultFromInnerLoop}); + // rewriter.setInsertionPointAfter(forOp2); + rewriter.create(loc, constant25, alloc, iv); + // #set2 = affine_set<(d0, d1)[]: (d0 - 5 >= 0, d1- 5 >= 0 ) > + // affine.for %arg0 = 0 to 10 { + // %N = len(output) + // %4 = affine.for %arg1 = 0 to 10 { + // affine.if #set2(%arg0 , %arg1 )[%N] { + // %1 = const 5 + // %2 = const 3 + // %3 = arith.mulf %1 , %2 + // affine.yield %3 + // } + // } + // affine.store %4, alloc[%arg0] + // } + // rewriter.create(loc, ValueRange{constant25}); + // rewriter.setInsertionPointAfter(ifOp); + // rewriter.create(loc, ifOp.getResult(0) , alloc, iv); - // create 2nd loop - // use loop inductn variable for 2nd loop - // use if condition on 2nd loop inductn variable - // get the result of inner for loop and store at output + // try to add the affine.If condition + // create affine.If , + // use integer set to represent the condition + // check the AffineArgs + // affine.if operation contains two regions for the “then” and “else” clauses + // each region of affine.if must contain a single block with no args and + // terminated by affine.yield op + // if affine.if defines no values --> no need for affine.yield - affine::AffineForOp forOp2 = rewriter.create(loc, - lb, ub, step ); - rewriter.setInsertionPointToStart(forOp2.getBody()); - auto iv2 = forOp2.getInductionVar(); - AffineExpr dimExpr2 = rewriter.getAffineDimExpr(1) - rewriter.getAffineConstantExpr(6); - IntegerSet set2 = IntegerSet::get(1, 0, {dimExpr,dimExpr2}, {false}); + // affineIf.setConditional(set1, forOp1.getInductionVar()); + // start then "block" + // "then" block - auto ifOp = rewriter.create( loc, set2 , ValueRange{iv} , false /*no else*/ ); - rewriter.setInsertionPointToStart(ifOp.getThenBlock()); - Value constant25 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(25)); - Value resultFromInnerLoop = rewriter.create(loc, constant25 , constant25); + // rewriter.create(loc, constant25); + llvm::errs() << "LINE = " << __LINE__ << "\n"; + // Back to parentOp -- ifOp stops here + // rewriter.setInsertionPointAfter(ifOp); - // rewriter.setInsertionPointAfter(forOp2); - // rewriter.setInsertionPointToEnd(forOp2->getBlock()); - // rewriter.create(loc, constant25 , alloc, iv2); - // rewriter.create(loc, ValueRange{resultFromInnerLoop}); - // rewriter.setInsertionPointAfter(ifOp); - // rewriter.create(loc, ValueRange{resultFromInnerLoop}); - // rewriter.setInsertionPointAfter(forOp2); - rewriter.create(loc, constant25 , alloc, iv); - // #set2 = affine_set<(d0, d1)[]: (d0 - 5 >= 0, d1- 5 >= 0 ) > - // affine.for %arg0 = 0 to 10 { - // %N = len(output) - // %4 = affine.for %arg1 = 0 to 10 { - // affine.if #set2(%arg0 , %arg1 )[%N] { - // %1 = const 5 - // %2 = const 3 - // %3 = arith.mulf %1 , %2 - // affine.yield %3 - // } - // } - // affine.store %4, alloc[%arg0] - // } - - - - // rewriter.create(loc, ValueRange{constant25}); - // rewriter.setInsertionPointAfter(ifOp); - // rewriter.create(loc, ifOp.getResult(0) , alloc, iv); - - //try to add the affine.If condition - //create affine.If , - // use integer set to represent the condition - //check the AffineArgs - // affine.if operation contains two regions for the “then” and “else” clauses - //each region of affine.if must contain a single block with no args and terminated by affine.yield op - // if affine.if defines no values --> no need for affine.yield - - // affineIf.setConditional(set1, forOp1.getInductionVar()); - //start then "block" - // "then" block - - // rewriter.create(loc, constant25); - llvm::errs() << "LINE = " << __LINE__ << "\n"; - //Back to parentOp -- ifOp stops here - // rewriter.setInsertionPointAfter(ifOp); - - llvm::errs() << "LINE = " << __LINE__ << " xx\n"; + llvm::errs() << "LINE = " << __LINE__ << " xx\n"; #endif #if TryMultiDimLoopAndAffineMap - // here, we have to use iter - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0] ; - int64_t step = 1; - - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - - affine::AffineForOp forOp1 = rewriter.create(loc, - lb, ub, step ); - - rewriter.setInsertionPointToStart(forOp1.getBody()); - auto iv = forOp1.getInductionVar(); - - //create loadOp - FIRFilterResponseOpAdaptor firOpAdaptor(operands); - - Value loadInput = rewriter.create(loc, firOpAdaptor.getLhs() , iv); - - //create another loop -- - affine::AffineForOp forOp2 = rewriter.create(loc, - lb, ub, step , ValueRange{loadInput} ); - - rewriter.setInsertionPointToStart(forOp2.getBody()); - auto iv2 = forOp2.getInductionVar(); - - //Use AffineMap for affine.load alloc_9[%arg0 - %arg1] - AffineExpr OuterIndx = rewriter.getAffineDimExpr(0); - AffineExpr InnerIndx = rewriter.getAffineDimExpr(1); - AffineMap addMap = AffineMap::get(2, 0, OuterIndx - InnerIndx); - // auto outputIndex = rewriter.create(loc, addMap , ValueRange{iv,iv2}); - - // Value constant15 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(15)); - - - // Value loadFilter = rewriter.create(loc, firOpAdaptor.getRhs() , addMap, ValueRange{iv2,iv}); - Value loadFilter = rewriter.create(loc, firOpAdaptor.getRhs() , addMap, ValueRange{iv,iv2}); - // Value loadFilter = rewriter.create(loc, firOpAdaptor.getRhs() , outputIndex); - // get iterArg - auto getIterArg = forOp2.getBody()->getArgument(1); - auto sumNext = rewriter.create(loc, getIterArg, loadFilter); - - - - //store the result to output - // rewriter.create(loc, sumNext, alloc, iv ); - rewriter.create(loc, ValueRange{sumNext}); - rewriter.setInsertionPointAfter(forOp2); - rewriter.create(loc, forOp2.getResult(0), alloc, iv ); - // - //yield the - //inside the forOp body --> create the operations & then close the body - // OpBuilder::InsertionGuard guard(rewriter); - // Initial sum set to 0. - // affine.for %arg0 = 0 to 10 { - // %1 = affine.load input[%arg0] - // %4 = affine.for %arg1 = 0 to 10 step 1 - // iter_args(%sum_iter = %1) { - // %2 = affine.load filter[%arg1] - // %3 = arith.add sum_iter , %2 - // affine.yield %3 : f64 - // } - // affine.store %4, output[%arg0] - // } - - - // Inside the loop body: + // here, we have to use iter + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); + + rewriter.setInsertionPointToStart(forOp1.getBody()); + auto iv = forOp1.getInductionVar(); + + // create loadOp + FIRFilterResponseOpAdaptor firOpAdaptor(operands); + + Value loadInput = + rewriter.create(loc, firOpAdaptor.getLhs(), iv); + + // create another loop -- + affine::AffineForOp forOp2 = rewriter.create( + loc, lb, ub, step, ValueRange{loadInput}); + + rewriter.setInsertionPointToStart(forOp2.getBody()); + auto iv2 = forOp2.getInductionVar(); + + // Use AffineMap for affine.load alloc_9[%arg0 - %arg1] + AffineExpr OuterIndx = rewriter.getAffineDimExpr(0); + AffineExpr InnerIndx = rewriter.getAffineDimExpr(1); + AffineMap addMap = AffineMap::get(2, 0, OuterIndx - InnerIndx); + // auto outputIndex = rewriter.create(loc, addMap , + // ValueRange{iv,iv2}); + + // Value constant15 = rewriter.create(loc, + // rewriter.getF64Type(), rewriter.getF64FloatAttr(15)); + + // Value loadFilter = rewriter.create(loc, firOpAdaptor.getRhs() + // , addMap, ValueRange{iv2,iv}); + Value loadFilter = rewriter.create(loc, firOpAdaptor.getRhs(), + addMap, ValueRange{iv, iv2}); + // Value loadFilter = rewriter.create(loc, firOpAdaptor.getRhs() + // , outputIndex); get iterArg + auto getIterArg = forOp2.getBody()->getArgument(1); + auto sumNext = rewriter.create(loc, getIterArg, loadFilter); + + // store the result to output + // rewriter.create(loc, sumNext, alloc, iv ); + rewriter.create(loc, ValueRange{sumNext}); + rewriter.setInsertionPointAfter(forOp2); + rewriter.create(loc, forOp2.getResult(0), alloc, iv); + // + // yield the + // inside the forOp body --> create the operations & then close the body + // OpBuilder::InsertionGuard guard(rewriter); + // Initial sum set to 0. + // affine.for %arg0 = 0 to 10 { + // %1 = affine.load input[%arg0] + // %4 = affine.for %arg1 = 0 to 10 step 1 + // iter_args(%sum_iter = %1) { + // %2 = affine.load filter[%arg1] + // %3 = arith.add sum_iter , %2 + // affine.yield %3 : f64 + // } + // affine.store %4, output[%arg0] + // } - - llvm::errs() << "LINE = " << __LINE__ << "\n"; + // Inside the loop body: + llvm::errs() << "LINE = " << __LINE__ << "\n"; #endif #if TryMultiDimLoopAndAffineSet - // here, we have to use iter - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0] ; - int64_t step = 1; - - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - - affine::AffineForOp forOp1 = rewriter.create(loc, - lb, ub, step ); - - rewriter.setInsertionPointToStart(forOp1.getBody()); - auto iv = forOp1.getInductionVar(); - - //create loadOp - FIRFilterResponseOpAdaptor firOpAdaptor(operands); - - Value loadInput = rewriter.create(loc, firOpAdaptor.getLhs() , iv); - - //create another loop -- - affine::AffineForOp forOp2 = rewriter.create(loc, - lb, ub, step , ValueRange{loadInput} ); - - rewriter.setInsertionPointToStart(forOp2.getBody()); - auto iv2 = forOp2.getInductionVar(); - - //Use AffineMap for affine.load alloc_9[%arg0 - %arg1] - AffineExpr OuterIndx = rewriter.getAffineDimExpr(0); - AffineExpr InnerIndx = rewriter.getAffineDimExpr(1); - AffineMap addMap = AffineMap::get(2, 0, OuterIndx - InnerIndx); - auto outputIndex = rewriter.create(loc, addMap , ValueRange{iv,iv2}); - - // Value constant15 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(15)); - AffineExpr dimExpr = OuterIndx - InnerIndx; - IntegerSet set1 = IntegerSet::get(2, 0, {dimExpr}, {false}); - - auto ifOp = rewriter.create( loc, set1 , ValueRange{iv,iv2} , false /*no else*/ ); - rewriter.setInsertionPointToStart(ifOp.getThenBlock()); - // Value loadFilter = rewriter.create(loc, firOpAdaptor.getRhs() , addMap, ValueRange{iv2,iv}); - Value loadFilter = rewriter.create(loc, firOpAdaptor.getRhs() , addMap, ValueRange{iv,iv2}); - // get iterArg - auto getIterArg = forOp2.getBody()->getArgument(1); - auto sumNext = rewriter.create(loc, loadFilter, loadFilter); - // rewriter.create(loc, sumNext, alloc, iv ); - rewriter.create(loc, ValueRange{sumNext}); - - //store the result to output - // rewriter.create(loc, sumNext, alloc, iv ); - rewriter.setInsertionPointAfter(ifOp); - rewriter.create(loc, ValueRange{sumNext}); - rewriter.setInsertionPointAfter(forOp2); - rewriter.create(loc, forOp2.getResult(0), alloc, iv ); - // - //yield the - //inside the forOp body --> create the operations & then close the body - // OpBuilder::InsertionGuard guard(rewriter); - // Initial sum set to 0. - // affine.for %arg0 = 0 to 10 { - // %1 = affine.load input[%arg0] - // %4 = affine.for %arg1 = 0 to 10 step 1 - // iter_args(%sum_iter = %1) { - // %2 = affine.load filter[%arg1] - // %3 = arith.add sum_iter , %2 - // affine.yield %3 : f64 - // } - // affine.store %4, output[%arg0] - // } - - - // Inside the loop body: + // here, we have to use iter + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); + + rewriter.setInsertionPointToStart(forOp1.getBody()); + auto iv = forOp1.getInductionVar(); + + // create loadOp + FIRFilterResponseOpAdaptor firOpAdaptor(operands); + + Value loadInput = + rewriter.create(loc, firOpAdaptor.getLhs(), iv); + + // create another loop -- + affine::AffineForOp forOp2 = rewriter.create( + loc, lb, ub, step, ValueRange{loadInput}); + + rewriter.setInsertionPointToStart(forOp2.getBody()); + auto iv2 = forOp2.getInductionVar(); + + // Use AffineMap for affine.load alloc_9[%arg0 - %arg1] + AffineExpr OuterIndx = rewriter.getAffineDimExpr(0); + AffineExpr InnerIndx = rewriter.getAffineDimExpr(1); + AffineMap addMap = AffineMap::get(2, 0, OuterIndx - InnerIndx); + auto outputIndex = + rewriter.create(loc, addMap, ValueRange{iv, iv2}); + + // Value constant15 = rewriter.create(loc, + // rewriter.getF64Type(), rewriter.getF64FloatAttr(15)); + AffineExpr dimExpr = OuterIndx - InnerIndx; + IntegerSet set1 = IntegerSet::get(2, 0, {dimExpr}, {false}); + + auto ifOp = rewriter.create( + loc, set1, ValueRange{iv, iv2}, false /*no else*/); + rewriter.setInsertionPointToStart(ifOp.getThenBlock()); + // Value loadFilter = rewriter.create(loc, firOpAdaptor.getRhs() + // , addMap, ValueRange{iv2,iv}); + Value loadFilter = rewriter.create(loc, firOpAdaptor.getRhs(), + addMap, ValueRange{iv, iv2}); + // get iterArg + auto getIterArg = forOp2.getBody()->getArgument(1); + auto sumNext = rewriter.create(loc, loadFilter, loadFilter); + // rewriter.create(loc, sumNext, alloc, iv ); + rewriter.create(loc, ValueRange{sumNext}); + + // store the result to output + // rewriter.create(loc, sumNext, alloc, iv ); + rewriter.setInsertionPointAfter(ifOp); + rewriter.create(loc, ValueRange{sumNext}); + rewriter.setInsertionPointAfter(forOp2); + rewriter.create(loc, forOp2.getResult(0), alloc, iv); + // + // yield the + // inside the forOp body --> create the operations & then close the body + // OpBuilder::InsertionGuard guard(rewriter); + // Initial sum set to 0. + // affine.for %arg0 = 0 to 10 { + // %1 = affine.load input[%arg0] + // %4 = affine.for %arg1 = 0 to 10 step 1 + // iter_args(%sum_iter = %1) { + // %2 = affine.load filter[%arg1] + // %3 = arith.add sum_iter , %2 + // affine.yield %3 : f64 + // } + // affine.store %4, output[%arg0] + // } - - llvm::errs() << "LINE = " << __LINE__ << "\n"; + // Inside the loop body: + llvm::errs() << "LINE = " << __LINE__ << "\n"; #endif #if TryFIRFilter - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0]; - int64_t step = 1; - - affine::AffineForOp forOp1 = rewriter.create(loc, - lb, ub, step ); - rewriter.setInsertionPointToStart(forOp1.getBody()); - auto iv = forOp1.getInductionVar(); - - // Value sum0 = rewriter.create(loc, rewriter.getF64Type(), - // rewriter.getF64FloatAttr(0)); - //get filter len - // auto tensorTypeFilter = llvm::cast((*op->getOperand(1))); //operand_type_end - // auto tensorTypeFilter = llvm::cast((*op->operand_type_begin())); - auto operandIt = op->operand_type_begin(); - auto tensorTypeInput = llvm::cast(*operandIt); - int64_t ubForInput = tensorTypeInput.getShape()[0]; - //get second operand - operandIt = operandIt + 1; - - // auto tensorTypeFilter = llvm::cast((*op->operand_type_begin())); //operandIt - auto tensorTypeFilter = llvm::cast(*operandIt); - int64_t ubForFilter = tensorTypeFilter.getShape()[0]; - - // llvm::errs() << "ubForFilter= " << ubForFilter << "\n"; - //create a constant for sum - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - affine::AffineForOp forOp2 = rewriter.create(loc, - lb, ubForFilter, step , ValueRange{constant0}); - rewriter.setInsertionPointToStart(forOp2.getBody()); - auto iv2 = forOp2.getInductionVar(); - - auto getIterArg = forOp2.getBody()->getArgument(1); //forOp1.getIterOperands(); - - // AffineExpr dimExpr = rewriter.getAffineDimExpr(0); - AffineExpr dimExpr2 = rewriter.getAffineDimExpr(0) - rewriter.getAffineDimExpr(1); - //n-k <= inputLen -1 or, k-n >= 1 - inputLen ie, k - n + inputLen - 1 >= 0 - AffineExpr ExprForUpperBoundCheck = rewriter.getAffineConstantExpr(ubForInput) + rewriter.getAffineDimExpr(1) - - rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(1) ; - IntegerSet set2 = IntegerSet::get(2, 0, {dimExpr2,ExprForUpperBoundCheck}, {false, false}); - - //use typeRange too: - Type floatType = rewriter.getF64Type(); - // if n-k >= 0 - auto ifOp = rewriter.create( loc, TypeRange{floatType}, set2 , ValueRange{iv,iv2} , true /*else*/ ); - rewriter.setInsertionPointToStart(ifOp.getThenBlock()); - - AffineMap addMap = AffineMap::get(2, 0, dimExpr2); - // auto inputIndex = rewriter.create(loc, addMap , ValueRange{iv,iv2}); + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOp1.getBody()); + auto iv = forOp1.getInductionVar(); + + // Value sum0 = rewriter.create(loc, rewriter.getF64Type(), + // rewriter.getF64FloatAttr(0)); + // get filter len + // auto tensorTypeFilter = llvm::cast((*op->getOperand(1))); + // //operand_type_end auto tensorTypeFilter = + // llvm::cast((*op->operand_type_begin())); + auto operandIt = op->operand_type_begin(); + auto tensorTypeInput = llvm::cast(*operandIt); + int64_t ubForInput = tensorTypeInput.getShape()[0]; + // get second operand + operandIt = operandIt + 1; + + // auto tensorTypeFilter = + // llvm::cast((*op->operand_type_begin())); //operandIt + auto tensorTypeFilter = llvm::cast(*operandIt); + int64_t ubForFilter = tensorTypeFilter.getShape()[0]; + + // llvm::errs() << "ubForFilter= " << ubForFilter << "\n"; + // create a constant for sum + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + affine::AffineForOp forOp2 = rewriter.create( + loc, lb, ubForFilter, step, ValueRange{constant0}); + rewriter.setInsertionPointToStart(forOp2.getBody()); + auto iv2 = forOp2.getInductionVar(); + + auto getIterArg = + forOp2.getBody()->getArgument(1); // forOp1.getIterOperands(); + + // AffineExpr dimExpr = rewriter.getAffineDimExpr(0); + AffineExpr dimExpr2 = + rewriter.getAffineDimExpr(0) - rewriter.getAffineDimExpr(1); + // n-k <= inputLen -1 or, k-n >= 1 - inputLen ie, k - n + inputLen - 1 >= 0 + AffineExpr ExprForUpperBoundCheck = + rewriter.getAffineConstantExpr(ubForInput) + + rewriter.getAffineDimExpr(1) - rewriter.getAffineDimExpr(0) - + rewriter.getAffineConstantExpr(1); + IntegerSet set2 = + IntegerSet::get(2, 0, {dimExpr2, ExprForUpperBoundCheck}, {false, false}); + + // use typeRange too: + Type floatType = rewriter.getF64Type(); + // if n-k >= 0 + auto ifOp = rewriter.create( + loc, TypeRange{floatType}, set2, ValueRange{iv, iv2}, true /*else*/); + rewriter.setInsertionPointToStart(ifOp.getThenBlock()); + + AffineMap addMap = AffineMap::get(2, 0, dimExpr2); + // auto inputIndex = rewriter.create(loc, addMap , + // ValueRange{iv,iv2}); + + FIRFilterResponseOpAdaptor firOpAdaptor(operands); + Value loadInput = rewriter.create(loc, firOpAdaptor.getLhs(), + addMap, ValueRange{iv, iv2}); + + rewriter.create(loc, ValueRange{loadInput}); + // else block + rewriter.setInsertionPointToStart(ifOp.getElseBlock()); + Value const0ForElse = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + rewriter.create(loc, ValueRange{const0ForElse}); + rewriter.setInsertionPointAfter(ifOp); + + // load filter and then mult and then sum + Value loadFilter = + rewriter.create(loc, firOpAdaptor.getRhs(), iv2); + // Value constant25 = rewriter.create(loc, + // rewriter.getF64Type(), + // rewriter.getF64FloatAttr(25)); + Value filterMulInput = + rewriter.create(loc, ifOp.getResult(0), loadFilter); + Value sumNext = + rewriter.create(loc, filterMulInput, getIterArg); + rewriter.create(loc, ValueRange{sumNext}); + // rewriter.setInsertionPointToEnd(forOp2->getBlock()); + rewriter.setInsertionPointAfter(forOp2); + rewriter.create(loc, forOp2.getResult(0), alloc, iv); + rewriter.setInsertionPointAfter(forOp1); + + // ifOp->dump(); + + // FIRFilterResponse code -- x[n] , h[n] + + // iterate for output + // start with sum=0 + // iterate for filter len + // check for input_indx must be within bounds + // load filter and input[indx] + // multiply them + // add this to sum + // update output with sum + + // inside the forOp body --> create the operations & then close the body + // OpBuilder::InsertionGuard guard(rewriter); + + // start adding operations like a arith::constant = 100.0 to the body of + // forOp1 + // Inside the loop body: + + // #set2 = affine_set<(d0, d1)[]: (d0 - 5 >= 0, d1- 5 >= 0 ) > + // affine.for %arg0 = 0 to 10 { + // %N = len(output) + // %4 = affine.for %arg1 = 0 to 10 { + // affine.if #set2(%arg0 , %arg1 )[%N] { + // %1 = const 5 + // %2 = const 3 + // %3 = arith.mulf %1 , %2 + // affine.yield %3 + // } + // } + // affine.store %4, alloc[%arg0] + // } - FIRFilterResponseOpAdaptor firOpAdaptor(operands); - Value loadInput = rewriter.create(loc, firOpAdaptor.getLhs(), addMap , ValueRange{iv,iv2}); + // rewriter.create(loc, ValueRange{constant25}); + // rewriter.setInsertionPointAfter(ifOp); + // rewriter.create(loc, ifOp.getResult(0) , alloc, iv); - rewriter.create(loc, ValueRange{loadInput}); - //else block - rewriter.setInsertionPointToStart(ifOp.getElseBlock()); - Value const0ForElse = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - rewriter.create(loc, ValueRange{const0ForElse}); - rewriter.setInsertionPointAfter(ifOp); + // try to add the affine.If condition + // create affine.If , + // use integer set to represent the condition + // check the AffineArgs + // affine.if operation contains two regions for the “then” and “else” clauses + // each region of affine.if must contain a single block with no args and + // terminated by affine.yield op + // if affine.if defines no values --> no need for affine.yield - //load filter and then mult and then sum - Value loadFilter = rewriter.create(loc, firOpAdaptor.getRhs() , iv2); - // Value constant25 = rewriter.create(loc, rewriter.getF64Type(), - // rewriter.getF64FloatAttr(25)); - Value filterMulInput = rewriter.create(loc, ifOp.getResult(0) , loadFilter); - Value sumNext = rewriter.create(loc, filterMulInput, getIterArg); - rewriter.create(loc, ValueRange{sumNext}); - // rewriter.setInsertionPointToEnd(forOp2->getBlock()); - rewriter.setInsertionPointAfter(forOp2); - rewriter.create(loc, forOp2.getResult(0) , alloc, iv); - rewriter.setInsertionPointAfter(forOp1); - - // ifOp->dump(); - - - //FIRFilterResponse code -- x[n] , h[n] - - //iterate for output - //start with sum=0 - //iterate for filter len - //check for input_indx must be within bounds - //load filter and input[indx] - //multiply them - //add this to sum - //update output with sum - - - - //inside the forOp body --> create the operations & then close the body - // OpBuilder::InsertionGuard guard(rewriter); - - //start adding operations like a arith::constant = 100.0 to the body of forOp1 - // Inside the loop body: - - - // #set2 = affine_set<(d0, d1)[]: (d0 - 5 >= 0, d1- 5 >= 0 ) > - // affine.for %arg0 = 0 to 10 { - // %N = len(output) - // %4 = affine.for %arg1 = 0 to 10 { - // affine.if #set2(%arg0 , %arg1 )[%N] { - // %1 = const 5 - // %2 = const 3 - // %3 = arith.mulf %1 , %2 - // affine.yield %3 - // } - // } - // affine.store %4, alloc[%arg0] - // } - - - - // rewriter.create(loc, ValueRange{constant25}); - // rewriter.setInsertionPointAfter(ifOp); - // rewriter.create(loc, ifOp.getResult(0) , alloc, iv); - - //try to add the affine.If condition - //create affine.If , - // use integer set to represent the condition - //check the AffineArgs - // affine.if operation contains two regions for the “then” and “else” clauses - //each region of affine.if must contain a single block with no args and terminated by affine.yield op - // if affine.if defines no values --> no need for affine.yield - - // affineIf.setConditional(set1, forOp1.getInductionVar()); - //start then "block" - // "then" block - - // rewriter.create(loc, constant25); - // llvm::errs() << "LINE = " << __LINE__ << "\n"; - //Back to parentOp -- ifOp stops here - // rewriter.setInsertionPointAfter(ifOp); - - // llvm::errs() << "LINE = " << __LINE__ << " xx\n"; + // affineIf.setConditional(set1, forOp1.getInductionVar()); + // start then "block" + // "then" block + // rewriter.create(loc, constant25); + // llvm::errs() << "LINE = " << __LINE__ << "\n"; + // Back to parentOp -- ifOp stops here + // rewriter.setInsertionPointAfter(ifOp); + // llvm::errs() << "LINE = " << __LINE__ << " xx\n"; #endif - // Terminate the loop body with affine.yield. - // rewriter.create(loc); - + // Terminate the loop body with affine.yield. + // rewriter.create(loc); // Replace this operation with the generated alloc. rewriter.replaceOp(op, alloc); @@ -989,197 +1029,207 @@ namespace { // ToyToAffine RewritePatterns: FFT1DImg operations //===----------------------------------------------------------------------===// - struct FFT1DImgConjSymmOpLowering : public ConversionPattern { FFT1DImgConjSymmOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::FFT1DImgConjSymmOp::getOperationName(), 1, ctx) {} + : ConversionPattern(dsp::FFT1DImgConjSymmOp::getOperationName(), 1, ctx) { + } LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y[k] = y_real[k] + j *y_img[k] - // y_img = sumOver_n(x[n]*sin[2*pi * k *n/N ] * -1 - - // For k=0: - //y[0] = 0 - - // for k=1 to (N+1)/2 - // sum = 0 - // for n=0 to N - // sum = sum + x[n] * sin(2*pi*k*n/N) - //y[k] = -1 * sum - //y[N-k] = sum - //init output mem for y_real & y_img as 0 - //iterate for output from k=0 to last - //iterate for all x from n=0 to last - //perform the calculations : ie x[n] * cos[2*pi * k *n/N ] and sum and store them at y[k] - // - // replace this upsampling op with the output_mem_allocation op - - DEBUG_PRINT_NO_ARGS() ; - - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + + // Pseudo-code: + // y[k] = y_real[k] + j *y_img[k] + // y_img = sumOver_n(x[n]*sin[2*pi * k *n/N ] * -1 + + // For k=0: + // y[0] = 0 + + // for k=1 to (N+1)/2 + // sum = 0 + // for n=0 to N + // sum = sum + x[n] * sin(2*pi*k*n/N) + // y[k] = -1 * sum + // y[N-k] = sum + // init output mem for y_real & y_img as 0 + // iterate for output from k=0 to last + // iterate for all x from n=0 to last + // perform the calculations : ie x[n] * cos[2*pi * k *n/N ] and sum and + // store them at y[k] + // + // replace this upsampling op with the output_mem_allocation op + + DEBUG_PRINT_NO_ARGS(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); // auto memRefType2 = convertTensorToMemRef(tensorType1); auto alloc_img = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); - + // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - //For loop -- iterate from 1 to last - int64_t lb = 0 ; + // For loop -- iterate from 1 to last + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; - int64_t ubBy2 = (ub+1)/2; + int64_t ubBy2 = (ub + 1) / 2; int64_t step = 1; - // affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); - // auto iv = forOp1.getInductionVar(); + // affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, + // step); auto iv = forOp1.getInductionVar(); // rewriter.setInsertionPointToStart(forOp1.getBody()); - // rewriter.create(loc, constant0, alloc_img, ValueRange{iv}); - // rewriter.setInsertionPointAfter(forOp1); - DEBUG_PRINT_NO_ARGS() ; - //for k=0 + // rewriter.create(loc, constant0, alloc_img, + // ValueRange{iv}); rewriter.setInsertionPointAfter(forOp1); + DEBUG_PRINT_NO_ARGS(); + // for k=0 Value Indx0 = rewriter.create(loc, 0); - rewriter.create(loc, constant0, alloc_img, ValueRange{Indx0}); + rewriter.create(loc, constant0, alloc_img, + ValueRange{Indx0}); - //loop for Y - affine::AffineForOp forOpY = rewriter.create(loc, lb+1, ubBy2, step); + // loop for Y + affine::AffineForOp forOpY = + rewriter.create(loc, lb + 1, ubBy2, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - - //loop for X - affine::AffineForOp forOpX = rewriter.create(loc, lb, ub, step, ValueRange{constant0}); + // loop for X + affine::AffineForOp forOpX = + rewriter.create(loc, lb, ub, step, ValueRange{constant0}); auto ivX = forOpX.getInductionVar(); - auto getIterArg = forOpX.getBody()->getArgument(1); + auto getIterArg = forOpX.getBody()->getArgument(1); rewriter.setInsertionPointToStart(forOpX.getBody()); - //load from X, & y1 & y2 + // load from X, & y1 & y2 FFT1DImgConjSymmOpAdaptor fft1DImgConjSymmAdaptor(operands); - Value inputX = rewriter.create(loc, fft1DImgConjSymmAdaptor.getInput(), ValueRange{ivX}); - // Value loadYImg = rewriter.create(loc, alloc_img, ValueRange{ivY}); + Value inputX = rewriter.create( + loc, fft1DImgConjSymmAdaptor.getInput(), ValueRange{ivX}); + // Value loadYImg = rewriter.create(loc, alloc_img, + // ValueRange{ivY}); + + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value k = + rewriter.create(loc, rewriter.getF64Type(), IndxY); - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value k = rewriter.create(loc, rewriter.getF64Type(), IndxY); + Value IndxX = rewriter.create( + loc, rewriter.getIntegerType(32), ivX); + Value i = + rewriter.create(loc, rewriter.getF64Type(), IndxX); - Value IndxX = rewriter.create(loc, rewriter.getIntegerType(32), ivX); - Value i = rewriter.create(loc, rewriter.getF64Type(), IndxX); + // get 2*pi * k * i / N + Value muli_k = rewriter.create(loc, k, i); - //get 2*pi * k * i / N - Value muli_k = rewriter.create(loc, k , i); - - Value const2pi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(6.28318530718)); - Value mul2piKI = rewriter.create(loc, const2pi , muli_k); + Value const2pi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(6.28318530718)); + Value mul2piKI = rewriter.create(loc, const2pi, muli_k); // getOperand().getType() - // auto inputTensorType = llvm::cast(op->getOperand(0).getType()); - float LengthOfInput = (float) ub; - Value N = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(LengthOfInput)); + // auto inputTensorType = + // llvm::cast(op->getOperand(0).getType()); + float LengthOfInput = (float)ub; + Value N = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(LengthOfInput)); // Value N = inputTensorType.getShape()[0]; - Value divIndxByN = rewriter.create(loc, mul2piKI, N ) ; - + Value divIndxByN = rewriter.create(loc, mul2piKI, N); + // Img part = -1 * Sum(x[i] * sin(div) ) Value GetSin = rewriter.create(loc, divIndxByN); - Value xMulSin = rewriter.create(loc, inputX , GetSin); - Value imgSum = rewriter.create(loc, getIterArg ,xMulSin) ; + Value xMulSin = rewriter.create(loc, inputX, GetSin); + Value imgSum = rewriter.create(loc, getIterArg, xMulSin); rewriter.create(loc, ValueRange{imgSum}); rewriter.setInsertionPointAfter(forOpX); - - //store imgSum at y[k] - rewriter.create(loc, forOpX.getResult(0), alloc_img, ValueRange{ivY}); - - //store -1 * imgSum at y[N-k] - AffineExpr ExprNminusK = rewriter.getAffineConstantExpr(ub) - rewriter.getAffineDimExpr(0); - AffineMap mapNminusK = AffineMap::get(1, 0 , ExprNminusK); - Value constMinus1 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(-1)); - Value NegImgSum = rewriter.create(loc, constMinus1 , forOpX.getResult(0)); - - rewriter.create(loc, NegImgSum, alloc_img, mapNminusK, ValueRange{ivY}); + + // store imgSum at y[k] + rewriter.create(loc, forOpX.getResult(0), alloc_img, + ValueRange{ivY}); + + // store -1 * imgSum at y[N-k] + AffineExpr ExprNminusK = + rewriter.getAffineConstantExpr(ub) - rewriter.getAffineDimExpr(0); + AffineMap mapNminusK = AffineMap::get(1, 0, ExprNminusK); + Value constMinus1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + Value NegImgSum = + rewriter.create(loc, constMinus1, forOpX.getResult(0)); + + rewriter.create(loc, NegImgSum, alloc_img, mapNminusK, + ValueRange{ivY}); rewriter.setInsertionPointAfter(forOpY); - //debug - // forOpX->dump(); - // forOpY->dump(); - // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - - - // affine.for %y = 0 to 4 { - // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> - // // affine.store %0, %alloc_real[%arg0] : memref<4xf64> - // affine.for %x = 0 to 4 { - // // CAcluations - // %1 = affine.load %alloc_3[%x] : memref<4xf64> - // %2 = affine.load %alloc_real[%y] : memref<4xf64> - // %3 = affine.load %alloc_img[%y] : memref<4xf64> - // // index cast for multiply - // %4 = arith.index_castui %y : index to i32 - // %k = arith.uitofp %4 : i32 to f64 - // %6 = arith.index_castui %x : index to i32 - // %i = arith.uitofp %6 : i32 to f64 - // // %8 = arith.index_castui %arg3 : index to i32 - // // %9 = arith.uitofp %8 : i32 to f64 - // // %10 = arith.index_castui %arg4 : index to i32 - // // %11 = arith.uitofp %10 : i32 to f64 - - // %mul_1 = arith.mulf %i, %k : f64 - // %mul = arith.mulf %mul_1, %cst_2pi : f64 - // // ixk / N - // %div = arith.divf %mul, %N : f64 - // // cos of the above - // %res_cos = math.cos %div : f64 - // // %16 = arith.addf %14, %15 : f64 - // // %res_sin = arith.mulf %16, %cst_0 : f64 - - // %res_sin = math.sin %div : f64 - // %real_prod = arith.mulf %1, %res_cos : f64 - // %img_prod_1 = arith.mulf %1, %res_sin : f64 - // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 - - // %real = arith.addf %2, %real_prod : f64 - // %img = arith.addf %3, %img_prod : f64 - // affine.store %real, %alloc_real[%y] : memref<4xf64> - // // dsp.print %alloc_real : memref<4xf64> - // affine.store %img, %alloc_img[%y] : memref<4xf64> - - // } - // } + // debug + // forOpX->dump(); + // forOpY->dump(); + // affine.for %y = 0 to 4 { + // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + + // affine.for %y = 0 to 4 { + // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> + // // affine.store %0, %alloc_real[%arg0] : memref<4xf64> + // affine.for %x = 0 to 4 { + // // CAcluations + // %1 = affine.load %alloc_3[%x] : memref<4xf64> + // %2 = affine.load %alloc_real[%y] : memref<4xf64> + // %3 = affine.load %alloc_img[%y] : memref<4xf64> + // // index cast for multiply + // %4 = arith.index_castui %y : index to i32 + // %k = arith.uitofp %4 : i32 to f64 + // %6 = arith.index_castui %x : index to i32 + // %i = arith.uitofp %6 : i32 to f64 + // // %8 = arith.index_castui %arg3 : index to i32 + // // %9 = arith.uitofp %8 : i32 to f64 + // // %10 = arith.index_castui %arg4 : index to i32 + // // %11 = arith.uitofp %10 : i32 to f64 + + // %mul_1 = arith.mulf %i, %k : f64 + // %mul = arith.mulf %mul_1, %cst_2pi : f64 + // // ixk / N + // %div = arith.divf %mul, %N : f64 + // // cos of the above + // %res_cos = math.cos %div : f64 + // // %16 = arith.addf %14, %15 : f64 + // // %res_sin = arith.mulf %16, %cst_0 : f64 + + // %res_sin = math.sin %div : f64 + // %real_prod = arith.mulf %1, %res_cos : f64 + // %img_prod_1 = arith.mulf %1, %res_sin : f64 + // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 + + // %real = arith.addf %2, %real_prod : f64 + // %img = arith.addf %3, %img_prod : f64 + // affine.store %real, %alloc_real[%y] : memref<4xf64> + // // dsp.print %alloc_real : memref<4xf64> + // affine.store %img, %alloc_img[%y] : memref<4xf64> + + // } + // } // rewriter.replaceOp(op, alloc_real); rewriter.replaceOp(op, alloc_img); - + return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: FFT1DRealSymmOp operations //===----------------------------------------------------------------------===// - struct FFT1DRealSymmOpLowering : public ConversionPattern { FFT1DRealSymmOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::FFT1DRealSymmOp::getOperationName(), 1, ctx) {} @@ -1188,164 +1238,177 @@ struct FFT1DRealSymmOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y[k] = sumOver_n(x[n]*cos[2*pi * k *n/N ] , 0<=k < (N+1)/2 - // & y[N-k] = y[k] (N+1)/2<= k< N - // For k=0: - //sum=0 - // for n= 0 to N - //sum = sum + x[n] - //y[0] = sum - - // for k=1 to (N+1)/2 - // sum = 0 - // for n=0 to N - // sum = sum + x[n] * cos(2*pi*k*n/N) - //y[k] = sum - //y[N-k] = sum - - //Actual definition - // y[k] = y_real[k] + j *y_img[k] - // y_real = sumOver_n(x[n]*cos[2*pi * k *n/N ] - // y_img = sumOver_n(x[n]*sin[2*pi * k *n/N ] * -1 - //init output mem for y_real & y_img as 0 - // replace this upsampling op with the output_mem_allocation op + // Pseudo-code: + // y[k] = sumOver_n(x[n]*cos[2*pi * k *n/N ] , 0<=k < (N+1)/2 + // & y[N-k] = y[k] (N+1)/2<= k< N + // For k=0: + // sum=0 + // for n= 0 to N + // sum = sum + x[n] + // y[0] = sum + // for k=1 to (N+1)/2 + // sum = 0 + // for n=0 to N + // sum = sum + x[n] * cos(2*pi*k*n/N) + // y[k] = sum + // y[N-k] = sum + + // Actual definition + // y[k] = y_real[k] + j *y_img[k] + // y_real = sumOver_n(x[n]*cos[2*pi * k *n/N ] + // y_img = sumOver_n(x[n]*sin[2*pi * k *n/N ] * -1 + // init output mem for y_real & y_img as 0 + // replace this upsampling op with the output_mem_allocation op // DEBUG_PRINT_NO_ARGS() ; - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - //iterate to result1 --not needed for now but for future reference - // auto tensorType1 = llvm::cast(*std::next(op->result_type_begin(), 1)); + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + // iterate to result1 --not needed for now but for future reference + // auto tensorType1 = + // llvm::cast(*std::next(op->result_type_begin(), 1)); + + // DEBUG_PRINT_NO_ARGS() ; + // tensorType.getShape()[0] + // llvm::errs() << "tensorType1.getShape()[0] " << tensorType1.getShape()[0] + // << " func= " << __func__ << "\n"; - // DEBUG_PRINT_NO_ARGS() ; - //tensorType.getShape()[0] - // llvm::errs() << "tensorType1.getShape()[0] " << tensorType1.getShape()[0] << " func= " << __func__ << "\n"; - - //allocation & deallocation for the result of this operation + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); // auto memRefType2 = convertTensorToMemRef(tensorType1); auto alloc_real = insertAllocAndDealloc(memRefType, loc, rewriter); - - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); - // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); + // affine.for %y = 0 to 4 { + // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - //For loop -- iterate from 1 to last - int64_t lb = 0 ; + // For loop -- iterate from 1 to last + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; - int64_t ubBy2 = (ub+1)/2; + int64_t ubBy2 = (ub + 1) / 2; int64_t step = 1; - //load from X, & y1 & y2 + // load from X, & y1 & y2 FFT1DRealSymmOpAdaptor fft1DRealSymmAdaptor(operands); - // affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); - // auto iv = forOp1.getInductionVar(); + // affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, + // step); auto iv = forOp1.getInductionVar(); // rewriter.setInsertionPointToStart(forOp1.getBody()); - // rewriter.create(loc, constant0, alloc_real, ValueRange{iv}); - // rewriter.setInsertionPointAfter(forOp1); - - //k=0 - //sum=0 - // for n= 0 to N - //sum = sum + x[n] - //y[0] = sum - affine::AffineForOp forOp2 = rewriter.create(loc, - lb, ub, step , ValueRange{constant0}); + // rewriter.create(loc, constant0, alloc_real, + // ValueRange{iv}); rewriter.setInsertionPointAfter(forOp1); + + // k=0 + // sum=0 + // for n= 0 to N + // sum = sum + x[n] + // y[0] = sum + affine::AffineForOp forOp2 = rewriter.create( + loc, lb, ub, step, ValueRange{constant0}); auto iv2 = forOp2.getInductionVar(); rewriter.setInsertionPointToStart(forOp2.getBody()); - //get previous sum - auto getIterArg1 = forOp2.getBody()->getArgument(1); - Value loadX = rewriter.create(loc, fft1DRealSymmAdaptor.getInput(), ValueRange{iv2}); + // get previous sum + auto getIterArg1 = forOp2.getBody()->getArgument(1); + Value loadX = rewriter.create( + loc, fft1DRealSymmAdaptor.getInput(), ValueRange{iv2}); Value sumNext1 = rewriter.create(loc, loadX, getIterArg1); rewriter.create(loc, ValueRange{sumNext1}); rewriter.setInsertionPointAfter(forOp2); - //store result for k=0 + // store result for k=0 Value Indx0 = rewriter.create(loc, 0); - rewriter.create(loc, forOp2.getResult(0), alloc_real, ValueRange{Indx0}); + rewriter.create(loc, forOp2.getResult(0), alloc_real, + ValueRange{Indx0}); // for k=1 to (N+1)/2 - // sum = 0 - // for n=0 to N - // sum = sum + x[n] * cos(2*pi*k*n/N) - //y[k] = sum - //y[N-k] = sum - //loop for Y ie, k - affine::AffineForOp forOpY = rewriter.create(loc, lb+1, ubBy2, step); + // sum = 0 + // for n=0 to N + // sum = sum + x[n] * cos(2*pi*k*n/N) + // y[k] = sum + // y[N-k] = sum + // loop for Y ie, k + affine::AffineForOp forOpY = + rewriter.create(loc, lb + 1, ubBy2, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //loop for X - affine::AffineForOp forOpX = rewriter.create(loc, lb, ub, step, ValueRange{constant0}); + // loop for X + affine::AffineForOp forOpX = + rewriter.create(loc, lb, ub, step, ValueRange{constant0}); auto ivX = forOpX.getInductionVar(); - //get sum - auto getIterArg = forOpX.getBody()->getArgument(1); + // get sum + auto getIterArg = forOpX.getBody()->getArgument(1); rewriter.setInsertionPointToStart(forOpX.getBody()); - //load from X, & y1 & y2 - Value inputX = rewriter.create(loc, fft1DRealSymmAdaptor.getInput(), ValueRange{ivX}); - // Value loadYReal = rewriter.create(loc, alloc_real, ValueRange{ivY}); - - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value k = rewriter.create(loc, rewriter.getF64Type(), IndxY); - - Value IndxX = rewriter.create(loc, rewriter.getIntegerType(32), ivX); - Value i = rewriter.create(loc, rewriter.getF64Type(), IndxX); - - //get 2*pi * k * i / N - Value muli_k = rewriter.create(loc, k , i); - - Value const2pi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(6.28318530718)); - Value mul2piKI = rewriter.create(loc, const2pi , muli_k); - - float LengthOfInput = (float) ub; - Value N = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(LengthOfInput)); + // load from X, & y1 & y2 + Value inputX = rewriter.create( + loc, fft1DRealSymmAdaptor.getInput(), ValueRange{ivX}); + // Value loadYReal = rewriter.create(loc, alloc_real, + // ValueRange{ivY}); + + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value k = + rewriter.create(loc, rewriter.getF64Type(), IndxY); + + Value IndxX = rewriter.create( + loc, rewriter.getIntegerType(32), ivX); + Value i = + rewriter.create(loc, rewriter.getF64Type(), IndxX); + + // get 2*pi * k * i / N + Value muli_k = rewriter.create(loc, k, i); + + Value const2pi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(6.28318530718)); + Value mul2piKI = rewriter.create(loc, const2pi, muli_k); + + float LengthOfInput = (float)ub; + Value N = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(LengthOfInput)); // Value N = inputTensorType.getShape()[0]; - Value divIndxByN = rewriter.create(loc, mul2piKI, N ) ; + Value divIndxByN = rewriter.create(loc, mul2piKI, N); // Real part = Sum(x[i] * cos(div) ) Value GetCos = rewriter.create(loc, divIndxByN); - Value xMulCos = rewriter.create(loc, inputX , GetCos); + Value xMulCos = rewriter.create(loc, inputX, GetCos); + + // realSu + Value sumNext = rewriter.create(loc, getIterArg, xMulCos); + // rewriter.create(loc, sumNext, alloc_real, + // ValueRange{ivX}); - //realSu - Value sumNext = rewriter.create(loc, getIterArg ,xMulCos) ; - // rewriter.create(loc, sumNext, alloc_real, ValueRange{ivX}); - // DEBUG_PRINT_NO_ARGS() ; rewriter.create(loc, ValueRange{sumNext}); rewriter.setInsertionPointAfter(forOpX); // forOpX->dump(); - //store realSum at y[k] - rewriter.create(loc, forOpX.getResult(0) , alloc_real, ValueRange{ivY}); + // store realSum at y[k] + rewriter.create(loc, forOpX.getResult(0), alloc_real, + ValueRange{ivY}); - //store realSum at y[N-k] - AffineExpr ExprNminusK = rewriter.getAffineConstantExpr(ub) - rewriter.getAffineDimExpr(0); - AffineMap mapNminusK = AffineMap::get(1, 0 , ExprNminusK); - rewriter.create(loc, forOpX.getResult(0), alloc_real, mapNminusK, ValueRange{ivY}); + // store realSum at y[N-k] + AffineExpr ExprNminusK = + rewriter.getAffineConstantExpr(ub) - rewriter.getAffineDimExpr(0); + AffineMap mapNminusK = AffineMap::get(1, 0, ExprNminusK); + rewriter.create(loc, forOpX.getResult(0), alloc_real, + mapNminusK, ValueRange{ivY}); rewriter.setInsertionPointAfter(forOpY); - //debug - // forOpX->dump(); - // forOpY->dump(); + // debug + // forOpX->dump(); + // forOpY->dump(); rewriter.replaceOp(op, alloc_real); - + return success(); } }; @@ -1353,138 +1416,150 @@ struct FFT1DRealSymmOpLowering : public ConversionPattern { //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: FIRFilterYSymmOptimizedOp operations //===----------------------------------------------------------------------===// -struct FIRFilterYSymmOptimizedOpLowering: public ConversionPattern { - FIRFilterYSymmOptimizedOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::FIRFilterYSymmOptimizedOp::getOperationName(), 1 , ctx) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - //dsp.FIRFilterYSymmOptimizedOp has 2 operands -- both of type tensor f64 - - //Get the location of FIRFilterYSymmOptimizedOp - auto loc = op->getLoc(); - - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation +struct FIRFilterYSymmOptimizedOpLowering : public ConversionPattern { + FIRFilterYSymmOptimizedOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::FIRFilterYSymmOptimizedOp::getOperationName(), 1, + ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + // dsp.FIRFilterYSymmOptimizedOp has 2 operands -- both of type tensor f64 + + // Get the location of FIRFilterYSymmOptimizedOp + auto loc = op->getLoc(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //Pseudo-code: - //N=lenY , M=lenX here, output is symm ie, y[n] = y[N-1-n] - //y[n] = x[n] conv x[-n] ie, x[M-1-n] ie, x2[n] - //y[n] = SumOverAllk x[k] * x2[n-k] , 0<=k(loc, - lb, ubBy2, step ); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ubBy2, step); rewriter.setInsertionPointToStart(forOp1.getBody()); auto iv = forOp1.getInductionVar(); - //for n=0 to N - // sum = 0, temp =0 - //for n=0 to (N+1)/2 - // sum =0 - //get filter len + // for n=0 to N + // sum = 0, temp =0 + // for n=0 to (N+1)/2 + // sum =0 + // get filter len auto operandIt = op->operand_type_begin(); auto tensorTypeInput = llvm::cast(*operandIt); int64_t ubForInput = tensorTypeInput.getShape()[0]; DEBUG_PRINT_NO_ARGS(); - DEBUG_PRINT_WITH_ARGS("ubForInput=" , ubForInput ); + DEBUG_PRINT_WITH_ARGS("ubForInput=", ubForInput); - //create a constant for sum - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - affine::AffineForOp forOp2 = rewriter.create(loc, - lb, ubForInput, step , ValueRange{constant0}); + // create a constant for sum + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + affine::AffineForOp forOp2 = rewriter.create( + loc, lb, ubForInput, step, ValueRange{constant0}); rewriter.setInsertionPointToStart(forOp2.getBody()); auto iv2 = forOp2.getInductionVar(); - //get sum - auto getIterArg = forOp2.getBody()->getArgument(1); + // get sum + auto getIterArg = forOp2.getBody()->getArgument(1); DEBUG_PRINT_NO_ARGS(); FIRFilterYSymmOptimizedOpAdaptor firFilterYSymmOpAdaptor(operands); - // if( 0<= M+k-n-1 =0 ie, 2 dimensions =n & k - //UpperBoundSet: M+k-n-1 <= M-1 ie, n-k>=0 - - //LowerBound Expr: M+k-n-1 >=0 ie, M-1 + k -n >= 0 - AffineExpr ExprLowerBound = rewriter.getAffineConstantExpr(ubForInput - 1) + rewriter.getAffineDimExpr(1) - - rewriter.getAffineDimExpr(0); - //UpperBoundSet: M+k-n-1 <= M-1 ie, n-k>=0 - AffineExpr ExprUpperBound = rewriter.getAffineDimExpr(0) - rewriter.getAffineDimExpr(1) ; - IntegerSet setForIf = IntegerSet::get(2,0, {ExprLowerBound , ExprUpperBound}, {false, false}); + // sum = sum + x[k] * x[M+k-n-1] + // For M+k-n-1 + // LowerBoundSet: M+k-n-1 >=0 ie, 2 dimensions =n & k + // UpperBoundSet: M+k-n-1 <= M-1 ie, n-k>=0 + + // LowerBound Expr: M+k-n-1 >=0 ie, M-1 + k -n >= 0 + AffineExpr ExprLowerBound = rewriter.getAffineConstantExpr(ubForInput - 1) + + rewriter.getAffineDimExpr(1) - + rewriter.getAffineDimExpr(0); + // UpperBoundSet: M+k-n-1 <= M-1 ie, n-k>=0 + AffineExpr ExprUpperBound = + rewriter.getAffineDimExpr(0) - rewriter.getAffineDimExpr(1); + IntegerSet setForIf = + IntegerSet::get(2, 0, {ExprLowerBound, ExprUpperBound}, {false, false}); DEBUG_PRINT_NO_ARGS(); // if( 0<= M+k-n-1 ( loc, TypeRange{floatType}, setForIf , ValueRange{iv,iv2} , true /*else*/ ); + auto ifOp = + rewriter.create(loc, TypeRange{floatType}, setForIf, + ValueRange{iv, iv2}, true /*else*/); rewriter.setInsertionPointToStart(ifOp.getThenBlock()); DEBUG_PRINT_NO_ARGS(); // sum = sum + x[k] * x[M+k-n-1] - //load x[M+k-n-1] - AffineMap mapMPlusKMinusNmin1 = AffineMap::get(2, 0 , ExprLowerBound); - Value loadInputIndx2 = rewriter.create(loc, firFilterYSymmOpAdaptor.getLhs(), mapMPlusKMinusNmin1 , ValueRange{iv,iv2}); + // load x[M+k-n-1] + AffineMap mapMPlusKMinusNmin1 = AffineMap::get(2, 0, ExprLowerBound); + Value loadInputIndx2 = + rewriter.create(loc, firFilterYSymmOpAdaptor.getLhs(), + mapMPlusKMinusNmin1, ValueRange{iv, iv2}); rewriter.create(loc, ValueRange{loadInputIndx2}); - //else return 0 + // else return 0 rewriter.setInsertionPointToStart(ifOp.getElseBlock()); - Value const0ForElse = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value const0ForElse = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); rewriter.create(loc, ValueRange{const0ForElse}); rewriter.setInsertionPointAfter(ifOp); - //outside if - //Now, sum = sum + val2 * x[k] - Value loadX = rewriter.create(loc, firFilterYSymmOpAdaptor.getLhs(), ValueRange{iv2}); + // outside if + // Now, sum = sum + val2 * x[k] + Value loadX = rewriter.create( + loc, firFilterYSymmOpAdaptor.getLhs(), ValueRange{iv2}); DEBUG_PRINT_NO_ARGS(); - //x[k] * x[M+k-n-1] here, val2 = x[M+k-n-1] - Value XMulReverseXIndx = rewriter.create(loc, loadX , ifOp.getResult(0)); - //sum = sum + x[k] * x[M+k-n-1] - Value sumNext = rewriter.create(loc, XMulReverseXIndx, getIterArg); + // x[k] * x[M+k-n-1] here, val2 = x[M+k-n-1] + Value XMulReverseXIndx = + rewriter.create(loc, loadX, ifOp.getResult(0)); + // sum = sum + x[k] * x[M+k-n-1] + Value sumNext = + rewriter.create(loc, XMulReverseXIndx, getIterArg); rewriter.create(loc, ValueRange{sumNext}); - + DEBUG_PRINT_NO_ARGS(); rewriter.setInsertionPointAfter(forOp2); // forOp2->dump(); DEBUG_PRINT_NO_ARGS(); - //y[n] = sum ie, y[n] = sumNext - rewriter.create(loc, forOp2.getResult(0) , alloc, iv); - //y[N-1-n] = sum - AffineExpr ExprNminus1minYn = rewriter.getAffineConstantExpr(ub - 1) - rewriter.getAffineDimExpr(0); - AffineMap mapNminus1minYn = AffineMap::get(1, 0 , ExprNminus1minYn); + // y[n] = sum ie, y[n] = sumNext + rewriter.create(loc, forOp2.getResult(0), alloc, iv); + // y[N-1-n] = sum + AffineExpr ExprNminus1minYn = + rewriter.getAffineConstantExpr(ub - 1) - rewriter.getAffineDimExpr(0); + AffineMap mapNminus1minYn = AffineMap::get(1, 0, ExprNminus1minYn); - rewriter.create(loc, forOp2.getResult(0) , alloc, mapNminus1minYn , ValueRange{iv}); + rewriter.create(loc, forOp2.getResult(0), alloc, + mapNminus1minYn, ValueRange{iv}); rewriter.setInsertionPointAfter(forOp1); DEBUG_PRINT_NO_ARGS(); - + rewriter.replaceOp(op, alloc); return success(); - } + } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: PaddingOp operations //===----------------------------------------------------------------------===// @@ -1497,93 +1572,97 @@ struct PaddingOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y[n] = x[n] 0<=n((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + + // Pseudo-code: + // y[n] = x[n] 0<=n((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - + DEBUG_PRINT_NO_ARGS(); - //construct affine loops for the input + // construct affine loops for the input PaddingOpAdaptor paddingOpAdaptor(operands); Value GetPadLenOperand = op->getOperand(2); - dsp::ConstantOp constantOp3rdArg = GetPadLenOperand.getDefiningOp(); + dsp::ConstantOp constantOp3rdArg = + GetPadLenOperand.getDefiningOp(); - if(!constantOp3rdArg){ + if (!constantOp3rdArg) { llvm::errs() << "Fail:padding op 3rd operand is not constant\n"; return failure(); } - DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue();; + DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue(); + ; auto elements1 = constant3rdValue.getValues(); float Padlen = elements1[0].getValueAsDouble(); - DEBUG_PRINT_WITH_ARGS("Padlen is" , Padlen); - //first from 0 <= i < N - auto inputType = llvm::dyn_cast(op->getOperand(0).getType()); - int64_t lb = 0 ; - int64_t ub = inputType.getShape()[0]; + DEBUG_PRINT_WITH_ARGS("Padlen is", Padlen); + // first from 0 <= i < N + auto inputType = + llvm::dyn_cast(op->getOperand(0).getType()); + int64_t lb = 0; + int64_t ub = inputType.getShape()[0]; int64_t step = 1; - DEBUG_PRINT_NO_ARGS(); - - //loop from 0 <= i < N - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + DEBUG_PRINT_NO_ARGS(); + + // loop from 0 <= i < N + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - Value InputX = rewriter.create(loc, paddingOpAdaptor.getInput(), ivY); - rewriter.create(loc, InputX, alloc, ivY); + Value InputX = + rewriter.create(loc, paddingOpAdaptor.getInput(), ivY); + rewriter.create(loc, InputX, alloc, ivY); rewriter.setInsertionPointAfter(forOpY); - //loop from N to N+PadLen + // loop from N to N+PadLen int64_t lb2 = ub; - int64_t ub2 = ub + (int64_t) Padlen; + int64_t ub2 = ub + (int64_t)Padlen; - affine::AffineForOp forOp2 = rewriter.create(loc, lb2, ub2, step); + affine::AffineForOp forOp2 = + rewriter.create(loc, lb2, ub2, step); auto iv2 = forOp2.getInductionVar(); rewriter.setInsertionPointToStart(forOp2.getBody()); - Value PaddingValue = rewriter.create(loc, paddingOpAdaptor.getPadValue(), ValueRange{}); //getPadValue - rewriter.create(loc, PaddingValue, alloc, iv2); + Value PaddingValue = rewriter.create( + loc, paddingOpAdaptor.getPadValue(), ValueRange{}); // getPadValue + rewriter.create(loc, PaddingValue, alloc, iv2); rewriter.setInsertionPointAfter(forOp2); - //debug - // forOpX->dump(); - // forOpY->dump(); - + // debug + // forOpX->dump(); + // forOpY->dump(); + + // %cst = arith.constant 6.2831853071800001 : f64 + // %cst_0 = arith.constant 4.600000e-01 : f64 + // %cst_1 = arith.constant 5.400000e-01 : f64 + // %cst_2 = arith.constant 4.000000e+00 : f64 + // %alloc = memref.alloc() : memref<4xf64> + // %alloc_3 = memref.alloc() : memref + // affine.store %cst_2, %alloc_3[] : memref + // affine.for %arg0 = 0 to 4 { + // %0 = arith.index_castui %arg0 : index to i32 + // %1 = arith.uitofp %0 : i32 to f64 + // %2 = arith.mulf %1, %cst : f64 + // %3 = arith.divf %2, %cst_2 : f64 + // %4 = math.cos %3 : f64 + // %5 = arith.mulf %4, %cst_0 : f64 + // %6 = arith.subf %cst_1, %5 : f64 + // affine.store %6, %alloc[%arg0] : memref<4xf64> + // } - // %cst = arith.constant 6.2831853071800001 : f64 - // %cst_0 = arith.constant 4.600000e-01 : f64 - // %cst_1 = arith.constant 5.400000e-01 : f64 - // %cst_2 = arith.constant 4.000000e+00 : f64 - // %alloc = memref.alloc() : memref<4xf64> - // %alloc_3 = memref.alloc() : memref - // affine.store %cst_2, %alloc_3[] : memref - // affine.for %arg0 = 0 to 4 { - // %0 = arith.index_castui %arg0 : index to i32 - // %1 = arith.uitofp %0 : i32 to f64 - // %2 = arith.mulf %1, %cst : f64 - // %3 = arith.divf %2, %cst_2 : f64 - // %4 = math.cos %3 : f64 - // %5 = arith.mulf %4, %cst_0 : f64 - // %6 = arith.subf %cst_1, %5 : f64 - // affine.store %6, %alloc[%arg0] : memref<4xf64> - // } - - - // } - // } + // } + // } rewriter.replaceOp(op, alloc); - + return success(); } }; @@ -1600,70 +1679,68 @@ struct ReverseInputOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - //output = 0 - //iterate for len = 0 to N - // output[i] = a[N-1-i] + // Pseudo-code: + // output = 0 + // iterate for len = 0 to N + // output[i] = a[N-1-i] - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); SmallVector steps(tensorType.getRank(), /*Value=*/1); - - //For loop + // For loop ReverseInputOpAdaptor reverseInputOpAdaptor(operands); // DEBUG_PRINT_NO_ARGS() ; - - int64_t lb = 0 ; + + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - //for loop - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + // for loop + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); - + // DEBUG_PRINT_NO_ARGS() ; //: N-1 - i - AffineExpr reverseIndxExpr = rewriter.getAffineConstantExpr(ub - 1) - rewriter.getAffineDimExpr(0); + AffineExpr reverseIndxExpr = + rewriter.getAffineConstantExpr(ub - 1) - rewriter.getAffineDimExpr(0); AffineMap addMap2 = AffineMap::get(1, 0, reverseIndxExpr); - //load x[N-1-i] + // load x[N-1-i] DEBUG_PRINT_NO_ARGS(); - Value loadInputFrmReverseIndx = rewriter.create(loc, reverseInputOpAdaptor.getInput(), addMap2 , ValueRange{iv}); - + Value loadInputFrmReverseIndx = rewriter.create( + loc, reverseInputOpAdaptor.getInput(), addMap2, ValueRange{iv}); - - //store the result at indx i + // store the result at indx i rewriter.create(loc, loadInputFrmReverseIndx, alloc, iv); rewriter.setInsertionPointAfter(forOp1); - //debug - // forOp1->dump(); - // affine.for %arg0 = 0 to 5 { - // %0 = affine.load %alloc_6[%arg0] : memref<5xf64> - // %1 = arith.mulf %0, %0 : f64 - // affine.store %1, %alloc_5[%arg0] : memref<5xf64> - // } + // debug + // forOp1->dump(); + // affine.for %arg0 = 0 to 5 { + // %0 = affine.load %alloc_6[%arg0] : memref<5xf64> + // %1 = arith.mulf %0, %0 : f64 + // affine.store %1, %alloc_5[%arg0] : memref<5xf64> + // } rewriter.replaceOp(op, alloc); - + return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: LengthOp operations //===----------------------------------------------------------------------===// @@ -1675,43 +1752,44 @@ struct LengthOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // output = len(input) + + // Pseudo-code: + // output = len(input) DEBUG_PRINT_NO_ARGS(); - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - //iterate to result1 --not needed for now but for future reference - - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + // iterate to result1 --not needed for now but for future reference + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - auto inputType = llvm::dyn_cast(op->getOperand(0).getType()); //op->getOperand( + auto inputType = llvm::dyn_cast( + op->getOperand(0).getType()); // op->getOperand( int64_t ub = inputType.getShape()[0]; - Value constantUb = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(ub)); - + Value constantUb = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(ub)); DEBUG_PRINT_WITH_ARGS("\nCheck for index --here"); - //load from X, using 2nd operand as index - // DEBUG_PRINT_WITH_ARGS("Indx is" , SecondValueInt); + // load from X, using 2nd operand as index + // DEBUG_PRINT_WITH_ARGS("Indx is" , SecondValueInt); Value constantIndx0 = rewriter.create(loc, 0); - rewriter.create(loc, constantUb, alloc, ValueRange{constantIndx0}); + rewriter.create(loc, constantUb, alloc, + ValueRange{constantIndx0}); + // debug + // forOpX->dump(); + // forOpY->dump(); + // affine.store %cst, %alloc_10[] : memref + // %0 = affine.load %alloc_11[4] : memref<10xf64> + // affine.store %0, %alloc[0] : memref<1xf64> - //debug - // forOpX->dump(); - // forOpY->dump(); - // affine.store %cst, %alloc_10[] : memref - // %0 = affine.load %alloc_11[4] : memref<10xf64> - // affine.store %0, %alloc[0] : memref<1xf64> - rewriter.replaceOp(op, alloc); - + return success(); } }; @@ -2032,227 +2110,260 @@ struct FFTImagOpLowering : public ConversionPattern { //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: FIRFilterResSymmOptimizedOp operations //===----------------------------------------------------------------------===// -struct FIRFilterResSymmOptimizedOpLowering: public ConversionPattern { - FIRFilterResSymmOptimizedOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::FIRFilterResSymmOptimizedOp::getOperationName(), 1 , ctx) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - //dsp.FIRFilterResSymmOptimizedOp has 2 operands -- both of type tensor f64 - - //Get the location of FIRFilterResSymmOptimizedOp - auto loc = op->getLoc(); - - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation +struct FIRFilterResSymmOptimizedOpLowering : public ConversionPattern { + FIRFilterResSymmOptimizedOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::FIRFilterResSymmOptimizedOp::getOperationName(), + 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + // dsp.FIRFilterResSymmOptimizedOp has 2 operands -- both of type tensor f64 + + // Get the location of FIRFilterResSymmOptimizedOp + auto loc = op->getLoc(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //Pseudo-Code - //y[n] = sum(h[k] .{ x[n-k] + x[n-(L-1-k)]}) + h[L-1/2].x[n-(L-1)/2] , k=0 to L-1/2 - // N = lenY , M = lenX , L = lenH - //for n=0 to N - // sum = 0, temp =0 - // for k = 0 to L-1/2 - //if 0 <= n-k < M - //val1 = x[n-k] else, val1 = 0 - // if 0 <= n+k - (L-1) < M - // val2 = x[n+k-(L-1)] else, val2 = 0 - //temp = val1 + val2 - // sum = sum + h[k] . temp - - //middle-one - // if 0 <= n - (L-1)/2 < M - // sum2 = sum + h[L-1/2] . x[n-(n - (L-1)/2)] - // y[n] = sum2 - - - - int64_t lb = 0 ; + // Pseudo-Code + // y[n] = sum(h[k] .{ x[n-k] + x[n-(L-1-k)]}) + h[L-1/2].x[n-(L-1)/2] , k=0 + // to L-1/2 + // N = lenY , M = lenX , L = lenH + // for n=0 to N + // sum = 0, temp =0 + // for k = 0 to L-1/2 + // if 0 <= n-k < M + // val1 = x[n-k] else, val1 = 0 + // if 0 <= n+k - (L-1) < M + // val2 = x[n+k-(L-1)] else, val2 = 0 + // temp = val1 + val2 + // sum = sum + h[k] . temp + + // middle-one + // if 0 <= n - (L-1)/2 < M + // sum2 = sum + h[L-1/2] . x[n-(n - (L-1)/2)] + // y[n] = sum2 + + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; DEBUG_PRINT_NO_ARGS(); - affine::AffineForOp forOp1 = rewriter.create(loc, - lb, ub, step ); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); rewriter.setInsertionPointToStart(forOp1.getBody()); auto iv = forOp1.getInductionVar(); - //for n=0 to N - // sum = 0, temp =0 - //get filter len - // auto tensorTypeFilter = llvm::cast((*op->getOperand(1))); //operand_type_end - // auto tensorTypeFilter = llvm::cast((*op->operand_type_begin())); + // for n=0 to N + // sum = 0, temp =0 + // get filter len + // auto tensorTypeFilter = + // llvm::cast((*op->getOperand(1))); //operand_type_end + // auto tensorTypeFilter = + // llvm::cast((*op->operand_type_begin())); auto operandIt = op->operand_type_begin(); auto tensorTypeInput = llvm::cast(*operandIt); int64_t ubForInput = tensorTypeInput.getShape()[0]; - //get second operand + // get second operand operandIt = operandIt + 1; - // auto tensorTypeFilter = llvm::cast((*op->operand_type_begin())); //operandIt + // auto tensorTypeFilter = + // llvm::cast((*op->operand_type_begin())); //operandIt auto tensorTypeFilter = llvm::cast(*operandIt); int64_t ubForFilter = tensorTypeFilter.getShape()[0]; DEBUG_PRINT_NO_ARGS(); // llvm::errs() << "ubForFilter= " << ubForFilter << "\n"; - //create a constant for sum - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - affine::AffineForOp forOp2 = rewriter.create(loc, - lb, ubForFilter/2, step , ValueRange{constant0}); + // create a constant for sum + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + affine::AffineForOp forOp2 = rewriter.create( + loc, lb, ubForFilter / 2, step, ValueRange{constant0}); rewriter.setInsertionPointToStart(forOp2.getBody()); auto iv2 = forOp2.getInductionVar(); - auto getIterArg = forOp2.getBody()->getArgument(1); //forOp1.getIterOperands(); + auto getIterArg = + forOp2.getBody()->getArgument(1); // forOp1.getIterOperands(); DEBUG_PRINT_NO_ARGS(); FIRFilterResSymmOptimizedOpAdaptor firFilterResSymmOpAdaptor(operands); - //if 0 <= n-k < M - //val1 = x[n-k] else, val1 = 0 - //For n-k - //if 0 <= n-k < M or, 0 <= n-k <= M -1 - AffineExpr d0, d1,s0, s1 ; + // if 0 <= n-k < M + // val1 = x[n-k] else, val1 = 0 + // For n-k + // if 0 <= n-k < M or, 0 <= n-k <= M -1 + AffineExpr d0, d1, s0, s1; bindDims(rewriter.getContext(), d0, d1); AffineExpr ExprNMinusK = d0 - d1; - AffineMap mapNMinusK = AffineMap::get(2, 0 , ExprNMinusK); + AffineMap mapNMinusK = AffineMap::get(2, 0, ExprNMinusK); // n-k <= M -1 or, n-k-(M-1) <= 0 - bindSymbols(rewriter.getContext() , s0, s1); - Value constantMMinus1Indx = rewriter.create(loc, ubForInput -1); + bindSymbols(rewriter.getContext(), s0, s1); + Value constantMMinus1Indx = + rewriter.create(loc, ubForInput - 1); - AffineExpr ExprNMinusKMinusMPlus1 = s0 - d0 + d1 ; - IntegerSet setForIf = IntegerSet::get(2,1, {ExprNMinusK , ExprNMinusKMinusMPlus1}, {false, false}); + AffineExpr ExprNMinusKMinusMPlus1 = s0 - d0 + d1; + IntegerSet setForIf = IntegerSet::get( + 2, 1, {ExprNMinusK, ExprNMinusKMinusMPlus1}, {false, false}); DEBUG_PRINT_NO_ARGS(); - //if 0 <= n-k <= M -1 - //use typeRange too: + // if 0 <= n-k <= M -1 + // use typeRange too: Type floatType = rewriter.getF64Type(); // if n-k >= 0 && n-k <= M -1 or, M-1 -n + k >= 0 - auto ifOp = rewriter.create( loc, TypeRange{floatType}, setForIf , ValueRange{iv,iv2, constantMMinus1Indx} , true /*else*/ ); + auto ifOp = rewriter.create( + loc, TypeRange{floatType}, setForIf, + ValueRange{iv, iv2, constantMMinus1Indx}, true /*else*/); rewriter.setInsertionPointToStart(ifOp.getThenBlock()); - //val1 = x[n-k] else, val1 = 0 - //load x[n-k] + // val1 = x[n-k] else, val1 = 0 + // load x[n-k] DEBUG_PRINT_NO_ARGS(); - Value loadInput = rewriter.create(loc, firFilterResSymmOpAdaptor.getLhs(), mapNMinusK , ValueRange{iv,iv2}); + Value loadInput = + rewriter.create(loc, firFilterResSymmOpAdaptor.getLhs(), + mapNMinusK, ValueRange{iv, iv2}); rewriter.create(loc, ValueRange{loadInput}); - //else block + // else block rewriter.setInsertionPointToStart(ifOp.getElseBlock()); - Value const0ForElse = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value const0ForElse = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); rewriter.create(loc, ValueRange{const0ForElse}); rewriter.setInsertionPointAfter(ifOp); - // if 0 <= n+k - (L-1) < M - // val2 = x[n+k-(L-1)] else, val2 = 0 - //val2 lower bound - // AffineExpr ExprNMinKMinLPlus1 = d0 - d1 - s0; //s0 = (L-1) => -s0 = -L+1 - // AffineExpr ExprLowerBoundVal2 = d0 - d1 - s0; //s0 = (L-1) => -s0 = -L+1 - //Val2 LowerBound: n+k - (L-1) >= 0 - AffineExpr ExprLowerBoundVal2 = rewriter.getAffineDimExpr(0) + rewriter.getAffineDimExpr(1) - - rewriter.getAffineConstantExpr(ubForFilter - 1); - //Val2 UpperBound: n+k - (L-1) <= M -1 ie, M - 1 + L -1 -k -n >= 0 ie, (M+L-2) - k -n >= 0 - // AffineExpr ExprUpperBoundVal2 = s0 + s1 + d1 - d0; //s1 = M+L-2 = L-1 + M -1 - AffineExpr ExprUpperBoundVal2 = rewriter.getAffineConstantExpr(ubForInput + ubForFilter - 2) - rewriter.getAffineDimExpr(1) - - rewriter.getAffineDimExpr(0); - //s0 = L -1 - // Value s0LMin1Indx = rewriter.create(loc, ubForFilter - 1); - // s1 = M + L -2 for val2 upperBound - // Value s1MPlusLPlus2Indx = rewriter.create(loc, ubForInput + ubForFilter - 2); - // Value s1MMin1Indx = rewriter.create(loc, ubForInput - 1); - - IntegerSet setForIf2 = IntegerSet::get(2,0, {ExprLowerBoundVal2 , ExprUpperBoundVal2}, {false, false}); - - auto ifOp2 = rewriter.create( loc, TypeRange{floatType}, setForIf2 , ValueRange{iv,iv2} , true /*else*/ ); + // val2 = x[n+k-(L-1)] else, val2 = 0 + // val2 lower bound + // AffineExpr ExprNMinKMinLPlus1 = d0 - d1 - s0; //s0 = (L-1) => -s0 = -L+1 + // AffineExpr ExprLowerBoundVal2 = d0 - d1 - s0; //s0 = (L-1) => -s0 = -L+1 + // Val2 LowerBound: n+k - (L-1) >= 0 + AffineExpr ExprLowerBoundVal2 = + rewriter.getAffineDimExpr(0) + rewriter.getAffineDimExpr(1) - + rewriter.getAffineConstantExpr(ubForFilter - 1); + // Val2 UpperBound: n+k - (L-1) <= M -1 ie, M - 1 + L -1 -k -n >= 0 ie, + // (M+L-2) - k -n >= 0 + // AffineExpr ExprUpperBoundVal2 = s0 + s1 + d1 - d0; //s1 = M+L-2 = L-1 + + // M -1 + AffineExpr ExprUpperBoundVal2 = + rewriter.getAffineConstantExpr(ubForInput + ubForFilter - 2) - + rewriter.getAffineDimExpr(1) - rewriter.getAffineDimExpr(0); + // s0 = L -1 + // Value s0LMin1Indx = rewriter.create(loc, + // ubForFilter - 1); s1 = M + L -2 for val2 upperBound Value + // s1MPlusLPlus2Indx = rewriter.create(loc, + // ubForInput + ubForFilter - 2); Value s1MMin1Indx = + // rewriter.create(loc, ubForInput - 1); + + IntegerSet setForIf2 = IntegerSet::get( + 2, 0, {ExprLowerBoundVal2, ExprUpperBoundVal2}, {false, false}); + + auto ifOp2 = rewriter.create( + loc, TypeRange{floatType}, setForIf2, ValueRange{iv, iv2}, + true /*else*/); rewriter.setInsertionPointToStart(ifOp2.getThenBlock()); - //val2 = x[n+k-(L-1)] else, val2 = 0 + // val2 = x[n+k-(L-1)] else, val2 = 0 AffineMap addMap2 = AffineMap::get(2, 0, ExprLowerBoundVal2); - //load x[n+k-(L-1)] + // load x[n+k-(L-1)] DEBUG_PRINT_NO_ARGS(); - Value loadInputForVal2 = rewriter.create(loc, firFilterResSymmOpAdaptor.getLhs(), addMap2 , ValueRange{iv,iv2 }); + Value loadInputForVal2 = rewriter.create( + loc, firFilterResSymmOpAdaptor.getLhs(), addMap2, ValueRange{iv, iv2}); rewriter.create(loc, ValueRange{loadInputForVal2}); - //else block + // else block rewriter.setInsertionPointToStart(ifOp2.getElseBlock()); - Value const0ForElse2 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value const0ForElse2 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); rewriter.create(loc, ValueRange{const0ForElse2}); rewriter.setInsertionPointAfter(ifOp2); - //temp = val1 + val2 - // sum = sum + h[k] . temp + // temp = val1 + val2 + // sum = sum + h[k] . temp + + Value Val1Plus2 = rewriter.create(loc, ifOp.getResult(0), + ifOp2.getResult(0)); - Value Val1Plus2 = rewriter.create(loc, ifOp.getResult(0) , ifOp2.getResult(0)); + // load filter and then mult and then sum + Value loadFilter = rewriter.create( + loc, firFilterResSymmOpAdaptor.getRhs(), iv2); - //load filter and then mult and then sum - Value loadFilter = rewriter.create(loc, firFilterResSymmOpAdaptor.getRhs() , iv2); - - Value filterMulInput = rewriter.create(loc, Val1Plus2 , loadFilter); - Value sumNext = rewriter.create(loc, filterMulInput, getIterArg); + Value filterMulInput = + rewriter.create(loc, Val1Plus2, loadFilter); + Value sumNext = + rewriter.create(loc, filterMulInput, getIterArg); rewriter.create(loc, ValueRange{sumNext}); // rewriter.setInsertionPointToEnd(forOp2->getBlock()); rewriter.setInsertionPointAfter(forOp2); DEBUG_PRINT_NO_ARGS(); - // Middle - point - // if 0 <= n - (L-1)/2 < M - // sum2 = sum + h[L-1/2] . x[n-(L-1)/2)] - // y[n] = sum2 + // Middle - point + // if 0 <= n - (L-1)/2 < M + // sum2 = sum + h[L-1/2] . x[n-(L-1)/2)] + // y[n] = sum2 // if 0 <= n - (L-1)/2 < M // AffineExpr ExprLowerBoundVal3 = d0 - s0; //s0 = (L-1)/2 // AffineExpr ExprUpperBoundVal3 = d0 - s1; //s1 = M+ (L-1)/2 - int64_t midFilterLen = (ubForFilter - 1)/2; - AffineExpr ExprLowerBoundVal3 = rewriter.getAffineDimExpr(0) - - rewriter.getAffineConstantExpr(midFilterLen); - //UpperBound: n - (L-1)/2 <= M - 1 ie, M-1 + mid - n - AffineExpr ExprUpperBoundVal3 = rewriter.getAffineConstantExpr(ubForInput + midFilterLen - 1) - - rewriter.getAffineDimExpr(0); + int64_t midFilterLen = (ubForFilter - 1) / 2; + AffineExpr ExprLowerBoundVal3 = + rewriter.getAffineDimExpr(0) - + rewriter.getAffineConstantExpr(midFilterLen); + // UpperBound: n - (L-1)/2 <= M - 1 ie, M-1 + mid - n + AffineExpr ExprUpperBoundVal3 = + rewriter.getAffineConstantExpr(ubForInput + midFilterLen - 1) - + rewriter.getAffineDimExpr(0); AffineMap addMap3 = AffineMap::get(1, 0, ExprLowerBoundVal3); - - IntegerSet setForIf3 = IntegerSet::get(1,0, {ExprLowerBoundVal3 , ExprUpperBoundVal3}, {false, false}); - auto ifOp3 = rewriter.create( loc, TypeRange{floatType}, setForIf3 , ValueRange{iv} , true /*else*/ ); + IntegerSet setForIf3 = IntegerSet::get( + 1, 0, {ExprLowerBoundVal3, ExprUpperBoundVal3}, {false, false}); + + auto ifOp3 = rewriter.create( + loc, TypeRange{floatType}, setForIf3, ValueRange{iv}, true /*else*/); rewriter.setInsertionPointToStart(ifOp3.getThenBlock()); - //val3 = x[n-(L-1)/2)] else, val3 = 0 - //load x[n-(L-1)/2)] + // val3 = x[n-(L-1)/2)] else, val3 = 0 + // load x[n-(L-1)/2)] DEBUG_PRINT_NO_ARGS(); - Value loadInputForVal3 = rewriter.create(loc, firFilterResSymmOpAdaptor.getLhs(), addMap3 , ValueRange{iv}); + Value loadInputForVal3 = rewriter.create( + loc, firFilterResSymmOpAdaptor.getLhs(), addMap3, ValueRange{iv}); rewriter.create(loc, ValueRange{loadInputForVal3}); - //else block + // else block rewriter.setInsertionPointToStart(ifOp3.getElseBlock()); - Value const0ForElse3 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value const0ForElse3 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); rewriter.create(loc, ValueRange{const0ForElse3}); rewriter.setInsertionPointAfter(ifOp3); - //sum2 = sum + h[L-1/2] . x[n-(L-1)/2)] - // y[n] = sum2 - //load filter and then mult and then sum - Value midFilterLenIndx = rewriter.create(loc, midFilterLen); - - Value loadFilterMid = rewriter.create(loc, firFilterResSymmOpAdaptor.getRhs() , midFilterLenIndx); - Value filterMulInput2 = rewriter.create(loc, ifOp3.getResult(0) , loadFilterMid); - Value sum2 = rewriter.create(loc, filterMulInput2, forOp2.getResult(0)); + // sum2 = sum + h[L-1/2] . x[n-(L-1)/2)] + // y[n] = sum2 + // load filter and then mult and then sum + Value midFilterLenIndx = + rewriter.create(loc, midFilterLen); + + Value loadFilterMid = rewriter.create( + loc, firFilterResSymmOpAdaptor.getRhs(), midFilterLenIndx); + Value filterMulInput2 = + rewriter.create(loc, ifOp3.getResult(0), loadFilterMid); + Value sum2 = rewriter.create(loc, filterMulInput2, + forOp2.getResult(0)); // rewriter.create(loc, forOp2.getResult(0) , alloc, iv); - rewriter.create(loc, sum2 , alloc, iv); + rewriter.create(loc, sum2, alloc, iv); rewriter.setInsertionPointAfter(forOp1); DEBUG_PRINT_NO_ARGS(); // ifOp->dump(); rewriter.replaceOp(op, alloc); return success(); - } + } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: RunLenEncodingOp operations - //===----------------------------------------------------------------------===// #define TryWhileLoop 0 #define TryLoadStoreForWhile 0 -#define TryPassIterIndex 0 //Not working +#define TryPassIterIndex 0 // Not working #define TryScf 0 -#define TryRLE 1 +#define TryRLE 1 struct RunLenEncodingOpLowering : public ConversionPattern { RunLenEncodingOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::RunLenEncodingOp::getOperationName(), 1, ctx) {} @@ -2261,351 +2372,388 @@ struct RunLenEncodingOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y_rle[i] = x[i] , if x[i] != x[i-1] , 1<=i 1 ie, for last element - // store the count value at k + N/2 + + // Pseudo-code: + // y_rle[i] = x[i] , if x[i] != x[i-1] , 1<=i 1 ie, for last element + // store the count value at k + N/2 DEBUG_PRINT_NO_ARGS(); - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); auto tensorType1 = RankedTensorType::get({1}, rewriter.getIndexType()); - //allocation & deallocation for the result of this operation + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto memRefType2 = convertTensorToMemRef(tensorType1); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); auto allocK = insertAllocAndDealloc(memRefType2, loc, rewriter); - // count = 1 , y[0] = x[0] , + // count = 1 , y[0] = x[0] , // loop from 0 to len RunLenEncodingOpAdaptor runLenEncodingAdaptor(operands); DEBUG_PRINT_NO_ARGS(); - - - - // len/2,k = n ie, len/2 - int64_t lb = 1 ; - int64_t N = tensorType.getShape()[0]; - int64_t ub = N/2 ; //output len is twice the input len + int64_t lb = 1; + int64_t N = tensorType.getShape()[0]; + int64_t ub = N / 2; // output len is twice the input len int64_t step = 1; int64_t k = 0; int64_t lb1 = 0; - Value const0 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); + Value const0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - //init all output memory with zero - affine::AffineForOp forOp1 = rewriter.create(loc, lb1, N, step); + // init all output memory with zero + affine::AffineForOp forOp1 = + rewriter.create(loc, lb1, N, step); DEBUG_PRINT_NO_ARGS(); auto iv1 = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); - rewriter.create(loc,const0, alloc, iv1 ); + rewriter.create(loc, const0, alloc, iv1); rewriter.setInsertionPointAfter(forOp1); DEBUG_PRINT_NO_ARGS(); - //load from X, + // load from X, Value constantIndx0 = rewriter.create(loc, 0); - Value inputX0 = rewriter.create(loc, runLenEncodingAdaptor.getInput(), ValueRange{constantIndx0}); - rewriter.create(loc, inputX0, alloc, ValueRange{constantIndx0}); - - -#if TryRLE - - // Initial count and k values as SSA values, count = 1 , k = 0 - // for i=1 to len/2 - // load prev = a[i-1] , current = a[i] - // if prev == current - // count = count + 1 - // else - // store count at index k + N/2 - // y[k + N/2] = count - // k = k +1 - // y[k] = current - // count = 1 - //for last element - // store the count value at k + N/2 - //y[k + N/2] = count - Value countVal = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(1)); + Value inputX0 = rewriter.create( + loc, runLenEncodingAdaptor.getInput(), ValueRange{constantIndx0}); + rewriter.create(loc, inputX0, alloc, + ValueRange{constantIndx0}); + +#if TryRLE + + // Initial count and k values as SSA values, count = 1 , k = 0 + // for i=1 to len/2 + // load prev = a[i-1] , current = a[i] + // if prev == current + // count = count + 1 + // else + // store count at index k + N/2 + // y[k + N/2] = count + // k = k +1 + // y[k] = current + // count = 1 + // for last element + // store the count value at k + N/2 + // y[k + N/2] = count + Value countVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); Value Indx0 = rewriter.create(loc, 0); Value IndxNBy2 = rewriter.create(loc, ub); Value kVal = rewriter.create(loc, k); - rewriter.create(loc, kVal, allocK , ValueRange{Indx0}); - + rewriter.create(loc, kVal, allocK, ValueRange{Indx0}); + Type floatType = rewriter.getF64Type(); // Type indexType = rewriter.getIndexType(); - //// // for i=1 to len/2 - // load prev = a[i-1] , current = a[i] - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step, ValueRange{countVal}); + //// // for i=1 to len/2 + // load prev = a[i-1] , current = a[i] + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step, ValueRange{countVal}); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); DEBUG_PRINT_NO_ARGS(); auto countArg = forOpY.getRegionIterArgs()[0]; - - Value current = rewriter.create(loc, runLenEncodingAdaptor.getInput(), ivY ); + + Value current = rewriter.create( + loc, runLenEncodingAdaptor.getInput(), ivY); // AffineExpr d0; bindDims(rewriter.getContext(), d0); AffineExpr ExprIMinus1 = d0 - rewriter.getAffineConstantExpr(1); - AffineMap mapExprIMinus1 = AffineMap::get(1,0, ExprIMinus1); - Value prev = rewriter.create(loc, runLenEncodingAdaptor.getInput(),mapExprIMinus1, ValueRange{ivY} ); + AffineMap mapExprIMinus1 = AffineMap::get(1, 0, ExprIMinus1); + Value prev = rewriter.create( + loc, runLenEncodingAdaptor.getInput(), mapExprIMinus1, ValueRange{ivY}); DEBUG_PRINT_NO_ARGS(); - // for i=1 to len/2 - // load prev = a[i-1] , current = a[i] - // if prev == current - // count = count + 1 - // else - // store count at index k + N/2 - // y[k + N/2] = count - // k = k +1 - // y[k] = current - // count = 1 - //for last element - // store the count value at k + N/2 - //y[k + N/2] = count - // TypeRange typeRange = TypeRange{rewriter.getF64Type() , rewriter.getIndexType()}; - // TypeRange typeRange = TypeRange({rewriter.getF64Type(), rewriter.getIndexType()}); - - // auto ifOp = rewriter.create(loc, TypeRange{rewriter.getF64Type(), rewriter.getIndexType()}, rewriter.create(loc, arith::CmpFPredicate::OEQ, prev, current), true, true); - auto CmpPrevCurrent = rewriter.create(loc, arith::CmpFPredicate::OEQ, prev, current); - - - //create if block with else condition + // for i=1 to len/2 + // load prev = a[i-1] , current = a[i] // if prev == current - // count = count + 1 - // auto ifOp = rewriter.create(loc, TypeRange{floatType , indexType}, CmpPrevCurrent , true /* else=1 */); - auto ifOp = rewriter.create(loc, TypeRange{floatType }, CmpPrevCurrent , true /* else=1 */); + // count = count + 1 + // else + // store count at index k + N/2 + // y[k + N/2] = count + // k = k +1 + // y[k] = current + // count = 1 + // for last element + // store the count value at k + N/2 + // y[k + N/2] = count + // TypeRange typeRange = TypeRange{rewriter.getF64Type() , + // rewriter.getIndexType()}; TypeRange typeRange = + // TypeRange({rewriter.getF64Type(), rewriter.getIndexType()}); + + // auto ifOp = rewriter.create(loc, + // TypeRange{rewriter.getF64Type(), rewriter.getIndexType()}, + // rewriter.create(loc, arith::CmpFPredicate::OEQ, prev, + // current), true, true); + auto CmpPrevCurrent = rewriter.create( + loc, arith::CmpFPredicate::OEQ, prev, current); + + // create if block with else condition + // if prev == current + // count = count + 1 + // auto ifOp = rewriter.create(loc, TypeRange{floatType , + // indexType}, CmpPrevCurrent , true /* else=1 */); + auto ifOp = rewriter.create(loc, TypeRange{floatType}, + CmpPrevCurrent, true /* else=1 */); rewriter.setInsertionPointToStart(ifOp.thenBlock()); DEBUG_PRINT_NO_ARGS(); - + auto CountPlusOne = rewriter.create(loc, countArg, countVal); DEBUG_PRINT_NO_ARGS(); - rewriter.create(loc, ValueRange{CountPlusOne} ); - // else - // store count at index k + N/2 - // y[k + N/2] = count - // k = k +1 - // y[k] = current - // count = 1 + rewriter.create(loc, ValueRange{CountPlusOne}); + // else + // store count at index k + N/2 + // y[k + N/2] = count + // k = k +1 + // y[k] = current + // count = 1 rewriter.setInsertionPointToStart(ifOp.elseBlock()); - // // out[k + N/2]= count - Value loadKVal = rewriter.create(loc, allocK, ValueRange{Indx0} ); + // // out[k + N/2]= count + Value loadKVal = + rewriter.create(loc, allocK, ValueRange{Indx0}); - Value kPlusNBy2 = rewriter.create(loc,rewriter.getIndexType(), loadKVal, IndxNBy2); + Value kPlusNBy2 = rewriter.create( + loc, rewriter.getIndexType(), loadKVal, IndxNBy2); rewriter.create(loc, countArg, alloc, kPlusNBy2); - //k = k+1 + // k = k+1 Value Indx1 = rewriter.create(loc, 1); - Value kPlusOne = rewriter.create(loc,rewriter.getIndexType(), loadKVal, Indx1); + Value kPlusOne = rewriter.create( + loc, rewriter.getIndexType(), loadKVal, Indx1); rewriter.create(loc, kPlusOne, allocK, ValueRange{Indx0}); // y[k + 1] = current rewriter.create(loc, current, alloc, kPlusOne); - + DEBUG_PRINT_NO_ARGS(); rewriter.create(loc, ValueRange{countVal}); rewriter.setInsertionPointAfter(ifOp); // ifOp.dump(); Value countRes = ifOp.getResult(0); - - - rewriter.create(loc, ValueRange{countRes }); + + rewriter.create(loc, ValueRange{countRes}); rewriter.setInsertionPointAfter(forOpY); // forOpY->dump(); - //check for last countArg value if countArg > 1, then store it at last + // check for last countArg value if countArg > 1, then store it at last Value finalCountArg = forOpY.getResult(0); - Value finalkArg = rewriter.create(loc, allocK, ValueRange{Indx0} ); - + Value finalkArg = + rewriter.create(loc, allocK, ValueRange{Indx0}); + // //if count>1 ,then store count at index k + N/2 - // auto ifOp1 = rewriter.create(loc, CmpCountGt1 , false /* else=0 */); + // auto ifOp1 = rewriter.create(loc, CmpCountGt1 , false /* + // else=0 */); // rewriter.setInsertionPointToStart(ifOp1.thenBlock()); DEBUG_PRINT_NO_ARGS(); - Value finalkPlusNBy2 = rewriter.create(loc,rewriter.getIndexType(), finalkArg, IndxNBy2); + Value finalkPlusNBy2 = rewriter.create( + loc, rewriter.getIndexType(), finalkArg, IndxNBy2); rewriter.create(loc, finalCountArg, alloc, finalkPlusNBy2); DEBUG_PRINT_NO_ARGS(); - // rewriter.setInsertionPointAfter(ifOp1); + // rewriter.setInsertionPointAfter(ifOp1); #endif #if TryPassIterIndex - //store k at its location & load and do addition to 1 and - Value kVal = rewriter.create(loc, ub-1); + // store k at its location & load and do addition to 1 and + Value kVal = rewriter.create(loc, ub - 1); Value Indx0 = rewriter.create(loc, 0); - auto kValStore = rewriter.create(loc, kVal, alloc2 , ValueRange{Indx0}); - + auto kValStore = + rewriter.create(loc, kVal, alloc2, ValueRange{Indx0}); + Type floatType = rewriter.getF64Type(); Type indexType = rewriter.getIndexType(); - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step, ValueRange{inputX0, kVal}); - // affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step, ValueRange{countVal, kVal}); + affine::AffineForOp forOpY = rewriter.create( + loc, lb, ub, step, ValueRange{inputX0, kVal}); + // affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, + // step, ValueRange{countVal, kVal}); auto ivY = forOpY.getInductionVar(); auto prev = forOpY.getRegionIterArgs()[0]; auto kArg = forOpY.getRegionIterArgs()[1]; rewriter.setInsertionPointToStart(forOpY.getBody()); - - Value Indx00 = rewriter.create(loc, 0); - Value current = rewriter.create(loc, runLenEncodingAdaptor.getInput(), ivY ); - Value loadKVal = rewriter.create(loc, alloc2, ValueRange{Indx0} ); - Value const1 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(1)); + + Value Indx00 = rewriter.create(loc, 0); + Value current = rewriter.create( + loc, runLenEncodingAdaptor.getInput(), ivY); + Value loadKVal = + rewriter.create(loc, alloc2, ValueRange{Indx0}); + Value const1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); Value currentPlus1 = rewriter.create(loc, prev, const1); - auto CmpPrevCurrent = rewriter.create(loc, arith::CmpFPredicate::OGE, current , const1 ); + auto CmpPrevCurrent = rewriter.create( + loc, arith::CmpFPredicate::OGE, current, const1); + // create if block with else condition + // if prev == current, count++ + auto ifOp = rewriter.create(loc, TypeRange{floatType}, + CmpPrevCurrent, true /* else=1 */); + // auto ifOp = rewriter.create(loc, CmpPrevCurrent , true /* + // else=1 */); - //create if block with else condition - // if prev == current, count++ - auto ifOp = rewriter.create(loc, TypeRange{floatType }, CmpPrevCurrent , true /* else=1 */); - // auto ifOp = rewriter.create(loc, CmpPrevCurrent , true /* else=1 */); - rewriter.setInsertionPointToStart(ifOp.thenBlock()); DEBUG_PRINT_NO_ARGS(); - //store count at N+i - // Value countPlus1 = rewriter.create(loc, countArg, countVal); + // store count at N+i + // Value countPlus1 = rewriter.create(loc, countArg, + // countVal); Value Indx1 = rewriter.create(loc, 1); - Value kPlusOne = rewriter.create(loc, rewriter.getIndexType() , kArg , Indx1); + Value kPlusOne = rewriter.create( + loc, rewriter.getIndexType(), kArg, Indx1); rewriter.create(loc, current, alloc, ValueRange{kArg}); - // rewriter.create(loc, current, alloc, ValueRange{kPlusOne}); + // rewriter.create(loc, current, alloc, + // ValueRange{kPlusOne}); rewriter.create(loc, current, alloc, ValueRange{kPlusOne}); rewriter.create(loc, kPlusOne, alloc2, ValueRange{Indx0}); rewriter.create(loc, ValueRange{currentPlus1}); rewriter.setInsertionPointToStart(ifOp.elseBlock()); rewriter.create(loc, currentPlus1, alloc, ValueRange{ivY}); - //yield the values - // rewriter.create(loc, ValueRange{kPlusOne }); + // yield the values + // rewriter.create(loc, ValueRange{kPlusOne }); rewriter.create(loc, ValueRange{currentPlus1}); rewriter.setInsertionPointAfter(ifOp); Value countRes = ifOp.getResult(0); - // Value kRes = ifOp.getResult(1); + // Value kRes = ifOp.getResult(1); // rewriter.create(loc, ValueRange{countRes,kRes }); - rewriter.create(loc, ValueRange{countRes, Indx00 }); + rewriter.create(loc, ValueRange{countRes, Indx00}); rewriter.setInsertionPointAfter(forOpY); - #endif #if TryWhileLoop auto kVal = rewriter.create(loc, k); - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step, ValueRange{kVal}); + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step, ValueRange{kVal}); auto ivY = forOpY.getInductionVar(); // auto countArg = forOpY.getRegionIterArgs()[0]; auto kArg = forOpY.getRegionIterArgs()[0]; rewriter.setInsertionPointToStart(forOpY.getBody()); - - Value current = rewriter.create(loc, runLenEncodingAdaptor.getInput(), ivY ); - //store count at N+i - // Value countPlus1 = rewriter.create(loc, countArg, countVal); + Value current = rewriter.create( + loc, runLenEncodingAdaptor.getInput(), ivY); + + // store count at N+i + // Value countPlus1 = rewriter.create(loc, countArg, + // countVal); Value Indx1 = rewriter.create(loc, 1); - Value kPlusOne = rewriter.create(loc,rewriter.getIndexType(), kArg, Indx1); - // Value constInt1 = rewriter.create(loc,rewriter.getI64IntegerAttr(1), rewriter.getI64Type() ); + Value kPlusOne = rewriter.create( + loc, rewriter.getIndexType(), kArg, Indx1); + // Value constInt1 = + // rewriter.create(loc,rewriter.getI64IntegerAttr(1), + // rewriter.getI64Type() ); - // Value kPlusOneIndex = rewriter.create(loc, rewriter.getIndexType(), kPlusOne); + // Value kPlusOneIndex = rewriter.create(loc, + // rewriter.getIndexType(), kPlusOne); // kPlusOne.dump(); - // Value kArg1 = rewriter.create(loc, rewriter.getIndexType(), kArg); + // Value kArg1 = rewriter.create(loc, + // rewriter.getIndexType(), kArg); - // rewriter.create(loc, countPlus1, alloc, mapExprNPlusI, ValueRange{kPlusOne}); - // rewriter.create(loc, countPlus1, alloc, ValueRange{kArg}); - // Store the result + // rewriter.create(loc, countPlus1, alloc, mapExprNPlusI, + // ValueRange{kPlusOne}); rewriter.create(loc, countPlus1, + // alloc, ValueRange{kArg}); Store the result // rewriter.create(loc, current, alloc, ivY); //working rewriter.create(loc, current, alloc, ValueRange{kArg}); - //yield the values - rewriter.create(loc, ValueRange{kPlusOne }); + // yield the values + rewriter.create(loc, ValueRange{kPlusOne}); // rewriter.create(loc, ValueRange{countPlus1 , kPlusOne}); rewriter.setInsertionPointAfter(forOpY); #endif #if TryLoadStoreForWhile - //store k at its location & load and do addition to 1 and - Value kVal = rewriter.create(loc, ub-1); + // store k at its location & load and do addition to 1 and + Value kVal = rewriter.create(loc, ub - 1); Value Indx0 = rewriter.create(loc, 0); - auto kValStore = rewriter.create(loc, kVal, alloc2 , ValueRange{Indx0}); - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step, ValueRange{inputX0}); + auto kValStore = + rewriter.create(loc, kVal, alloc2, ValueRange{Indx0}); + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step, ValueRange{inputX0}); auto ivY = forOpY.getInductionVar(); auto prev = forOpY.getRegionIterArgs()[0]; // auto kArg = forOpY.getRegionIterArgs()[0]; rewriter.setInsertionPointToStart(forOpY.getBody()); - - Value Indx00 = rewriter.create(loc, 0); - Value current = rewriter.create(loc, runLenEncodingAdaptor.getInput(), ivY ); - Value loadKVal = rewriter.create(loc, alloc2, ValueRange{Indx0} ); - Value const1 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(1)); + + Value Indx00 = rewriter.create(loc, 0); + Value current = rewriter.create( + loc, runLenEncodingAdaptor.getInput(), ivY); + Value loadKVal = + rewriter.create(loc, alloc2, ValueRange{Indx0}); + Value const1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); Value currentPlus1 = rewriter.create(loc, prev, const1); - auto CmpPrevCurrent = rewriter.create(loc, arith::CmpFPredicate::OGE, current , const1 ); + auto CmpPrevCurrent = rewriter.create( + loc, arith::CmpFPredicate::OGE, current, const1); + // create if block with else condition + // if prev == current, count++ + // auto ifOp = rewriter.create(loc, TypeRange{floatType , + // indexType}, CmpPrevCurrent , true /* else=1 */); + auto ifOp = + rewriter.create(loc, CmpPrevCurrent, true /* else=1 */); - //create if block with else condition - // if prev == current, count++ - // auto ifOp = rewriter.create(loc, TypeRange{floatType , indexType}, CmpPrevCurrent , true /* else=1 */); - auto ifOp = rewriter.create(loc, CmpPrevCurrent , true /* else=1 */); - rewriter.setInsertionPointToStart(ifOp.thenBlock()); DEBUG_PRINT_NO_ARGS(); - //store count at N+i - // Value countPlus1 = rewriter.create(loc, countArg, countVal); + // store count at N+i + // Value countPlus1 = rewriter.create(loc, countArg, + // countVal); Value Indx1 = rewriter.create(loc, 1); - Value kPlusOne = rewriter.create(loc, rewriter.getIndexType() , loadKVal , Indx1); + Value kPlusOne = rewriter.create( + loc, rewriter.getIndexType(), loadKVal, Indx1); rewriter.create(loc, current, alloc, ValueRange{ivY}); - // rewriter.create(loc, current, alloc, ValueRange{kPlusOne}); + // rewriter.create(loc, current, alloc, + // ValueRange{kPlusOne}); rewriter.create(loc, current, alloc, ValueRange{kPlusOne}); rewriter.create(loc, kPlusOne, alloc2, ValueRange{Indx0}); rewriter.setInsertionPointToStart(ifOp.elseBlock()); rewriter.create(loc, currentPlus1, alloc, ValueRange{ivY}); - //yield the values - // rewriter.create(loc, ValueRange{kPlusOne }); + // yield the values + // rewriter.create(loc, ValueRange{kPlusOne }); rewriter.setInsertionPointAfter(ifOp); - rewriter.create(loc, ValueRange{current }); + rewriter.create(loc, ValueRange{current}); rewriter.setInsertionPointAfter(forOpY); - #endif - //debug - // forOpY->dump(); - // affine.store %cst, %alloc_10[] : memref - // %0 = affine.load %alloc_11[4] : memref<10xf64> - // affine.store %0, %alloc[0] : memref<1xf64> - + // debug + // forOpY->dump(); + // affine.store %cst, %alloc_10[] : memref + // %0 = affine.load %alloc_11[4] : memref<10xf64> + // affine.store %0, %alloc[0] : memref<1xf64> + rewriter.replaceOp(op, alloc); - + return success(); } }; @@ -2616,26 +2764,27 @@ struct RunLenEncodingOpLowering : public ConversionPattern { struct LMSFilterResponseOpLowering : public ConversionPattern { LMSFilterResponseOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::LMSFilterResponseOp::getOperationName(), 1, ctx) {} + : ConversionPattern(dsp::LMSFilterResponseOp::getOperationName(), 1, + ctx) {} LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // for (int n = 0; n < NUM_SAMPLES; n++) { - // // Calculate the filter output y[n] - // y[n] = 0; - // for (int i = 0; i < FILTER_LENGTH; i++) { - // if (n - i >= 0) { // affine if - // y[n] = y[n] + (w[i] * x[n - i]); - // } - // } - + + // Pseudo-code: + // for (int n = 0; n < NUM_SAMPLES; n++) { + // // Calculate the filter output y[n] + // y[n] = 0; + // for (int i = 0; i < FILTER_LENGTH; i++) { + // if (n - i >= 0) { // affine if + // y[n] = y[n] + (w[i] * x[n - i]); + // } + // } + // // Calculate the error e[n] // e[n] = d[n] - y[n]; - + // // Update the filter weights w[i] // for (int i = 0; i < FILTER_LENGTH; i++) { // if (n - i >= 0) { @@ -2644,48 +2793,52 @@ struct LMSFilterResponseOpLowering : public ConversionPattern { // } // } - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); LMSFilterOpAdaptor lmsFilterAdaptor(operands); - // Value alpha = rewriter.create(loc, rewriter.getF64Type(), + // Value alpha = rewriter.create(loc, + // rewriter.getF64Type(), // rewriter.getF64FloatAttr(1)); - Value zeroval = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); - Value mu = rewriter.create(loc, lmsFilterAdaptor.getMu()); - - //For loop -- iterate from 0 to last - int64_t lb = 0 ; + Value zeroval = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value mu = rewriter.create(loc, lmsFilterAdaptor.getMu()); + + // For loop -- iterate from 0 to last + int64_t lb = 0; int64_t numSamples = tensorType.getShape()[0]; int64_t step = 1; Value GetFilterLOp = op->getOperand(3); - dsp::ConstantOp constantOp3rdArg = GetFilterLOp.getDefiningOp(); - DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue();; + dsp::ConstantOp constantOp3rdArg = + GetFilterLOp.getDefiningOp(); + DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue(); + ; auto elements1 = constant3rdValue.getValues(); float filterlenval = elements1[0].getValueAsDouble(); - auto FilterLength = (uint64_t) filterlenval; + auto FilterLength = (uint64_t)filterlenval; auto yMemRefType = MemRefType::get({numSamples}, rewriter.getF64Type()); auto wAlloc = rewriter.create(loc, yMemRefType); - affine::AffineForOp forOp1 = rewriter.create(loc, lb, numSamples, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, numSamples, step); auto iv = forOp1.getInductionVar(); - rewriter.setInsertionPointToStart(forOp1.getBody()); - //For affine expression: #map1 = affine_map<(%arg0)[] : (%arg0 - 1) + // For affine expression: #map1 = affine_map<(%arg0)[] : (%arg0 - 1) AffineExpr d0, d1, s0; bindDims(rewriter.getContext(), d0, d1); - // AffineExpr ExprForXSlice = rewriter.getAffineDimExpr(0) - rewriter.getAffineDimExpr(1); //d0 - d1; + // AffineExpr ExprForXSlice = rewriter.getAffineDimExpr(0) - + // rewriter.getAffineDimExpr(1); //d0 - d1; AffineExpr ExprForXSlice = d0 - d1; AffineMap addMapForLMSFilter = AffineMap::get(2, 0, ExprForXSlice); IntegerSet set1 = IntegerSet::get(2, 0, {ExprForXSlice}, {false}); @@ -2694,80 +2847,81 @@ struct LMSFilterResponseOpLowering : public ConversionPattern { // rewriter.create(loc, zeroval, alloc, ValueRange{iv}); // Allocate and initialize array for y // Value constantIndx0 = rewriter.create(loc, 0); - + rewriter.create(loc, zeroval, alloc, ValueRange{iv}); - affine::AffineForOp forOp2 = rewriter.create(loc, lb, FilterLength, step); + affine::AffineForOp forOp2 = + rewriter.create(loc, lb, FilterLength, step); auto iv2 = forOp2.getInductionVar(); rewriter.setInsertionPointToStart(forOp2.getBody()); - auto ifOp = rewriter.create( loc, set1 , ValueRange{iv,iv2} , false /*no else*/ ); + auto ifOp = rewriter.create( + loc, set1, ValueRange{iv, iv2}, false /*no else*/); rewriter.setInsertionPointToStart(ifOp.getThenBlock()); - Value inputX = rewriter.create(loc, lmsFilterAdaptor.getLhs(), addMapForLMSFilter, - ValueRange{iv,iv2}); - Value w = rewriter.create(loc, wAlloc, - ValueRange{iv2}); //memRefType + Value inputX = + rewriter.create(loc, lmsFilterAdaptor.getLhs(), + addMapForLMSFilter, ValueRange{iv, iv2}); + Value w = rewriter.create(loc, wAlloc, + ValueRange{iv2}); // memRefType - Value wmulx = rewriter.create(loc, inputX ,w ); + Value wmulx = rewriter.create(loc, inputX, w); Value ybefore = rewriter.create(loc, alloc, ValueRange{iv}); Value sumNext = rewriter.create(loc, wmulx, ybefore); rewriter.create(loc, sumNext, alloc, ValueRange{iv}); rewriter.setInsertionPointAfter(ifOp); rewriter.setInsertionPointAfter(forOp2); - // get e[n] = d[n] - y[n] - Value desiredX = rewriter.create(loc, lmsFilterAdaptor.getRhs(), ValueRange{iv}); - Value ynew = rewriter.create(loc, alloc, - ValueRange{iv}); - - Value err = rewriter.create(loc, desiredX ,ynew ); - - - - affine::AffineForOp forOp3 = rewriter.create(loc, lb, FilterLength, step); - auto iv3 = forOp3.getInductionVar(); - - rewriter.setInsertionPointToStart(forOp3.getBody()); - - auto ifOp2 = rewriter.create( loc, set1 , ValueRange{iv,iv3} , false /*no else*/ ); - rewriter.setInsertionPointToStart(ifOp2.getThenBlock()); - - Value inputX2 = rewriter.create(loc, lmsFilterAdaptor.getLhs(), addMapForLMSFilter, - ValueRange{iv,iv3}); - - Value Prevw2 = rewriter.create(loc, wAlloc, - ValueRange{iv3}); - - // f(u(n),e(n),μ)=μe(n)u∗(n) - Value mul1 = rewriter.create(loc, err ,inputX2 ); - Value mul2 = rewriter.create(loc, mu ,mul1 ); - - // FInal w[n] - Value answer = rewriter.create(loc, Prevw2 ,mul2 ); - - rewriter.create(loc, answer, wAlloc, ValueRange{iv3}); - rewriter.setInsertionPointAfter(ifOp2); - rewriter.setInsertionPointAfter(forOp3); - - rewriter.setInsertionPointAfter(forOp1); - //debug - // forOp1->dump(); + Value desiredX = rewriter.create( + loc, lmsFilterAdaptor.getRhs(), ValueRange{iv}); + Value ynew = rewriter.create(loc, alloc, ValueRange{iv}); + + Value err = rewriter.create(loc, desiredX, ynew); + + affine::AffineForOp forOp3 = + rewriter.create(loc, lb, FilterLength, step); + auto iv3 = forOp3.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp3.getBody()); + + auto ifOp2 = rewriter.create( + loc, set1, ValueRange{iv, iv3}, false /*no else*/); + rewriter.setInsertionPointToStart(ifOp2.getThenBlock()); + + Value inputX2 = + rewriter.create(loc, lmsFilterAdaptor.getLhs(), + addMapForLMSFilter, ValueRange{iv, iv3}); + + Value Prevw2 = rewriter.create(loc, wAlloc, ValueRange{iv3}); + + // f(u(n),e(n),μ)=μe(n)u∗(n) + Value mul1 = rewriter.create(loc, err, inputX2); + Value mul2 = rewriter.create(loc, mu, mul1); + + // FInal w[n] + Value answer = rewriter.create(loc, Prevw2, mul2); + + rewriter.create(loc, answer, wAlloc, ValueRange{iv3}); + rewriter.setInsertionPointAfter(ifOp2); + rewriter.setInsertionPointAfter(forOp3); + + rewriter.setInsertionPointAfter(forOp1); + // debug + // forOp1->dump(); rewriter.replaceOp(op, alloc); - + return success(); } -}; +}; //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: Quantization operations //===----------------------------------------------------------------------===// - struct QuantizationOpLowering : public ConversionPattern { QuantizationOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::QuantizationOp::getOperationName(), 1, ctx) {} @@ -2776,103 +2930,112 @@ struct QuantizationOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y_quantized[i] = Round(a[i] - min) / step) * step + min - // where, step = (max-min)/ NoOfLevels , NoOLevels = 2^NoOfBits - // steps: - // 1) given NoOfLevels - // 2) Then calculate stepSize = (Max-Min)/NoOfLevels - // 3) iterate for all the elements and calculate quantizedCoeff + // Pseudo-code: + // y_quantized[i] = Round(a[i] - min) / step) * step + min + // where, step = (max-min)/ NoOfLevels , NoOLevels = 2^NoOfBits - // GetLevelForVal = (a[i] - min)/step - // RoundedVal = arith.FPToSI(GetLevelForVal) - // QuantVal = RoundedVal * step + min_val + // steps: + // 1) given NoOfLevels + // 2) Then calculate stepSize = (Max-Min)/NoOfLevels + // 3) iterate for all the elements and calculate quantizedCoeff + + // GetLevelForVal = (a[i] - min)/step + // RoundedVal = arith.FPToSI(GetLevelForVal) + // QuantVal = RoundedVal * step + min_val DEBUG_PRINT_NO_ARGS(); - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //create another memory location for getting NoOfLevels + // create another memory location for getting NoOfLevels - // Value constant1 = rewriter.create(loc, rewriter.getF64Type(), + // Value constant1 = rewriter.create(loc, + // rewriter.getF64Type(), // rewriter.getF64FloatAttr(1)); - - //1) Then calculate stepSize = (Max-Min)/NoOfLevels - + // 1) Then calculate stepSize = (Max-Min)/NoOfLevels + QuantizationOpAdaptor quantizationAdaptor(operands); DEBUG_PRINT_NO_ARGS(); Value getMaxMemref = quantizationAdaptor.getMax(); - auto getMax = rewriter.create(loc, getMaxMemref, ValueRange{}); + auto getMax = + rewriter.create(loc, getMaxMemref, ValueRange{}); Value getMinMemref = quantizationAdaptor.getMin(); - auto getMin = rewriter.create(loc, getMinMemref, ValueRange{}); + auto getMin = + rewriter.create(loc, getMinMemref, ValueRange{}); Value getNLevelsMemref = quantizationAdaptor.getNlevels(); - auto getNlevels = rewriter.create(loc, getNLevelsMemref, ValueRange{}); - - Value MaxMinusMin = rewriter.create(loc, getMax ,getMin ); - Value StepSize = rewriter.create(loc, MaxMinusMin, getNlevels); + auto getNlevels = + rewriter.create(loc, getNLevelsMemref, ValueRange{}); + Value MaxMinusMin = rewriter.create(loc, getMax, getMin); + Value StepSize = + rewriter.create(loc, MaxMinusMin, getNlevels); // iterate for all the elements and calculate quantizedCoeff // GetLevelForVal = (a[i] - min)/step // RoundedVal = arith.FPToSI(GetLevelForVal) // QuantVal = RoundedVal * step + min_val - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0]; + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; int64_t step = 1; DEBUG_PRINT_NO_ARGS(); - - //for loop from 0 to len - // use iter_arg as passing value for the loop - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + + // for loop from 0 to len + // use iter_arg as passing value for the loop + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //Use iter_arg for taking prev_val - //Get iter_arg - + // Use iter_arg for taking prev_val + // Get iter_arg + // GetLevelForVal = (a[i] - min)/step - - // QuantVal = RoundedVal * step + min_val - Value inputX = rewriter.create(loc, quantizationAdaptor.getInput(), ivY ); - Value inputMinusMin = rewriter.create(loc, inputX, getMin ); - Value aMinusMinDivStep = rewriter.create(loc, inputMinusMin, StepSize ); + // QuantVal = RoundedVal * step + min_val + + Value inputX = + rewriter.create(loc, quantizationAdaptor.getInput(), ivY); + Value inputMinusMin = rewriter.create(loc, inputX, getMin); + Value aMinusMinDivStep = + rewriter.create(loc, inputMinusMin, StepSize); // RoundedVal = arith.FPToSI(GetLevelForVal) - Value RoundedVal = rewriter.create(loc,rewriter.getI64Type(), aMinusMinDivStep); - Value RoundValFloat = rewriter.create(loc, rewriter.getF64Type() , RoundedVal); + Value RoundedVal = rewriter.create( + loc, rewriter.getI64Type(), aMinusMinDivStep); + Value RoundValFloat = rewriter.create( + loc, rewriter.getF64Type(), RoundedVal); // QuantVal = RoundedVal * step + min_val - Value RoundedMulStep = rewriter.create(loc, RoundValFloat , StepSize); - Value QuantVal = rewriter.create(loc, RoundedMulStep, getMin); - rewriter.create(loc, QuantVal, alloc, ValueRange{ivY}); + Value RoundedMulStep = + rewriter.create(loc, RoundValFloat, StepSize); + Value QuantVal = + rewriter.create(loc, RoundedMulStep, getMin); + rewriter.create(loc, QuantVal, alloc, ValueRange{ivY}); rewriter.setInsertionPointAfter(forOpY); - - //debug - // forOpY->dump(); - // affine.store %cst, %alloc_10[] : memref - // %0 = affine.load %alloc_11[4] : memref<10xf64> - // affine.store %0, %alloc[0] : memref<1xf64> - + + // debug + // forOpY->dump(); + // affine.store %cst, %alloc_10[] : memref + // %0 = affine.load %alloc_11[4] : memref<10xf64> + // affine.store %0, %alloc[0] : memref<1xf64> + rewriter.replaceOp(op, alloc); - + return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: lmsFilter operations //===----------------------------------------------------------------------===// @@ -2885,20 +3048,20 @@ struct LMSFilterOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // for (int n = 0; n < NUM_SAMPLES; n++) { - // // Calculate the filter output y[n] - // y[n] = 0; - // for (int i = 0; i < FILTER_LENGTH; i++) { - // if (n - i >= 0) { // affine if - // y[n] = y[n] + (w[i] * x[n - i]); - // } - // } - + + // Pseudo-code: + // for (int n = 0; n < NUM_SAMPLES; n++) { + // // Calculate the filter output y[n] + // y[n] = 0; + // for (int i = 0; i < FILTER_LENGTH; i++) { + // if (n - i >= 0) { // affine if + // y[n] = y[n] + (w[i] * x[n - i]); + // } + // } + // // Calculate the error e[n] // e[n] = d[n] - y[n]; - + // // Update the filter weights w[i] // for (int i = 0; i < FILTER_LENGTH; i++) { // if (n - i >= 0) { @@ -2907,59 +3070,64 @@ struct LMSFilterOpLowering : public ConversionPattern { // } // } - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); LMSFilterOpAdaptor lmsFilterAdaptor(operands); - // Value alpha = rewriter.create(loc, rewriter.getF64Type(), + // Value alpha = rewriter.create(loc, + // rewriter.getF64Type(), // rewriter.getF64FloatAttr(1)); - Value zeroval = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); - Value mu = rewriter.create(loc, lmsFilterAdaptor.getMu()); - - //For loop -- iterate from 0 to last - int64_t lb = 0 ; + Value zeroval = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value mu = rewriter.create(loc, lmsFilterAdaptor.getMu()); + + // For loop -- iterate from 0 to last + int64_t lb = 0; int64_t numSamples = tensorType.getShape()[0]; int64_t step = 1; Value GetFilterLOp = op->getOperand(3); - dsp::ConstantOp constantOp3rdArg = GetFilterLOp.getDefiningOp(); - DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue();; + dsp::ConstantOp constantOp3rdArg = + GetFilterLOp.getDefiningOp(); + DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue(); + ; auto elements1 = constant3rdValue.getValues(); float filterlenval = elements1[0].getValueAsDouble(); - auto FilterLength = (uint64_t) filterlenval; + auto FilterLength = (uint64_t)filterlenval; Value GetItersLOp = op->getOperand(4); - dsp::ConstantOp constantOp4thArg = GetItersLOp.getDefiningOp(); - DenseElementsAttr constant4thValue = constantOp4thArg.getValue();; + dsp::ConstantOp constantOp4thArg = + GetItersLOp.getDefiningOp(); + DenseElementsAttr constant4thValue = constantOp4thArg.getValue(); + ; auto elements = constant4thValue.getValues(); float interationsval = elements[0].getValueAsDouble(); - auto TotalIterations = (uint64_t) interationsval; - - - + auto TotalIterations = (uint64_t)interationsval; + auto yMemRefType = MemRefType::get({numSamples}, rewriter.getF64Type()); auto yAlloc = rewriter.create(loc, yMemRefType); - affine::AffineForOp forOpiter = rewriter.create(loc, lb, TotalIterations, step); + affine::AffineForOp forOpiter = + rewriter.create(loc, lb, TotalIterations, step); rewriter.setInsertionPointToStart(forOpiter.getBody()); - affine::AffineForOp forOp1 = rewriter.create(loc, lb, numSamples, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, numSamples, step); auto iv = forOp1.getInductionVar(); - rewriter.setInsertionPointToStart(forOp1.getBody()); - //For affine expression: #map1 = affine_map<(%arg0)[] : (%arg0 - 1) + // For affine expression: #map1 = affine_map<(%arg0)[] : (%arg0 - 1) AffineExpr d0, d1, s0; bindDims(rewriter.getContext(), d0, d1); - // AffineExpr ExprForXSlice = rewriter.getAffineDimExpr(0) - rewriter.getAffineDimExpr(1); //d0 - d1; + // AffineExpr ExprForXSlice = rewriter.getAffineDimExpr(0) - + // rewriter.getAffineDimExpr(1); //d0 - d1; AffineExpr ExprForXSlice = d0 - d1; AffineMap addMapForLMSFilter = AffineMap::get(2, 0, ExprForXSlice); IntegerSet set1 = IntegerSet::get(2, 0, {ExprForXSlice}, {false}); @@ -2968,72 +3136,74 @@ struct LMSFilterOpLowering : public ConversionPattern { // rewriter.create(loc, zeroval, alloc, ValueRange{iv}); // Allocate and initialize array for y // Value constantIndx0 = rewriter.create(loc, 0); - + rewriter.create(loc, zeroval, yAlloc, ValueRange{iv}); - affine::AffineForOp forOp2 = rewriter.create(loc, lb, FilterLength, step); + affine::AffineForOp forOp2 = + rewriter.create(loc, lb, FilterLength, step); auto iv2 = forOp2.getInductionVar(); rewriter.setInsertionPointToStart(forOp2.getBody()); - auto ifOp = rewriter.create( loc, set1 , ValueRange{iv,iv2} , false /*no else*/ ); + auto ifOp = rewriter.create( + loc, set1, ValueRange{iv, iv2}, false /*no else*/); rewriter.setInsertionPointToStart(ifOp.getThenBlock()); - Value inputX = rewriter.create(loc, lmsFilterAdaptor.getLhs(), addMapForLMSFilter, - ValueRange{iv,iv2}); - Value Prevw = rewriter.create(loc, alloc, - ValueRange{iv2}); //memRefType + Value inputX = + rewriter.create(loc, lmsFilterAdaptor.getLhs(), + addMapForLMSFilter, ValueRange{iv, iv2}); + Value Prevw = rewriter.create(loc, alloc, + ValueRange{iv2}); // memRefType - Value wmulx = rewriter.create(loc, inputX ,Prevw ); + Value wmulx = rewriter.create(loc, inputX, Prevw); Value ybefore = rewriter.create(loc, yAlloc, ValueRange{iv}); Value sumNext = rewriter.create(loc, wmulx, ybefore); rewriter.create(loc, sumNext, yAlloc, ValueRange{iv}); rewriter.setInsertionPointAfter(ifOp); rewriter.setInsertionPointAfter(forOp2); - // get e[n] = d[n] - y[n] - Value desiredX = rewriter.create(loc, lmsFilterAdaptor.getRhs(), ValueRange{iv}); - Value ynew = rewriter.create(loc, yAlloc, - ValueRange{iv}); - - Value err = rewriter.create(loc, desiredX ,ynew ); - - - - affine::AffineForOp forOp3 = rewriter.create(loc, lb, FilterLength, step); - auto iv3 = forOp3.getInductionVar(); - - rewriter.setInsertionPointToStart(forOp3.getBody()); - - auto ifOp2 = rewriter.create( loc, set1 , ValueRange{iv,iv3} , false /*no else*/ ); - rewriter.setInsertionPointToStart(ifOp2.getThenBlock()); - - Value inputX2 = rewriter.create(loc, lmsFilterAdaptor.getLhs(), addMapForLMSFilter, - ValueRange{iv,iv3}); - - Value Prevw2 = rewriter.create(loc, alloc, - ValueRange{iv3}); - - // f(u(n),e(n),μ)=μe(n)u∗(n) - Value mul1 = rewriter.create(loc, err ,inputX2 ); - Value mul2 = rewriter.create(loc, mu ,mul1 ); - - // FInal w[n] - Value answer = rewriter.create(loc, Prevw2 ,mul2 ); - - rewriter.create(loc, answer, alloc, ValueRange{iv3}); - rewriter.setInsertionPointAfter(ifOp2); - rewriter.setInsertionPointAfter(forOp3); - - rewriter.setInsertionPointAfter(forOp1); - rewriter.setInsertionPointAfter(forOpiter); - //debug - // forOp1->dump(); + Value desiredX = rewriter.create( + loc, lmsFilterAdaptor.getRhs(), ValueRange{iv}); + Value ynew = rewriter.create(loc, yAlloc, ValueRange{iv}); + + Value err = rewriter.create(loc, desiredX, ynew); + + affine::AffineForOp forOp3 = + rewriter.create(loc, lb, FilterLength, step); + auto iv3 = forOp3.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp3.getBody()); + + auto ifOp2 = rewriter.create( + loc, set1, ValueRange{iv, iv3}, false /*no else*/); + rewriter.setInsertionPointToStart(ifOp2.getThenBlock()); + + Value inputX2 = + rewriter.create(loc, lmsFilterAdaptor.getLhs(), + addMapForLMSFilter, ValueRange{iv, iv3}); + + Value Prevw2 = rewriter.create(loc, alloc, ValueRange{iv3}); + + // f(u(n),e(n),μ)=μe(n)u∗(n) + Value mul1 = rewriter.create(loc, err, inputX2); + Value mul2 = rewriter.create(loc, mu, mul1); + + // FInal w[n] + Value answer = rewriter.create(loc, Prevw2, mul2); + + rewriter.create(loc, answer, alloc, ValueRange{iv3}); + rewriter.setInsertionPointAfter(ifOp2); + rewriter.setInsertionPointAfter(forOp3); + + rewriter.setInsertionPointAfter(forOp1); + rewriter.setInsertionPointAfter(forOpiter); + // debug + // forOp1->dump(); rewriter.replaceOp(op, alloc); - + return success(); } }; @@ -3042,7 +3212,6 @@ struct LMSFilterOpLowering : public ConversionPattern { // ToyToAffine RewritePatterns: Threshold operations //===----------------------------------------------------------------------===// - struct ThresholdOpLowering : public ConversionPattern { ThresholdOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::ThresholdOp::getOperationName(), 1, ctx) {} @@ -3051,207 +3220,227 @@ struct ThresholdOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y[n] = a[n] , if a[i] >= threshld or, a[i] <= -threshld - // = 0 , else + + // Pseudo-code: + // y[n] = a[n] , if a[i] >= threshld or, a[i] <= -threshld + // = 0 , else DEBUG_PRINT_NO_ARGS(); - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + // y[n] = a[n] , if a[i] >= threshld or, a[i] <= -threshld + // loop from 0 to len - //y[n] = a[n] , if a[i] >= threshld or, a[i] <= -threshld - //loop from 0 to len - - //load from X, + // load from X, ThresholdOpAdaptor thresholdAdaptor(operands); DEBUG_PRINT_NO_ARGS(); - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0]; + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; int64_t step = 1; DEBUG_PRINT_NO_ARGS(); - - //for loop from 0 to len(Output) - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + + // for loop from 0 to len(Output) + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - - Value inputX = rewriter.create(loc, thresholdAdaptor.getInput(), ivY ); - + + Value inputX = + rewriter.create(loc, thresholdAdaptor.getInput(), ivY); + // Load the threshold value from the memref auto thresholdMemRef = thresholdAdaptor.getThreshld(); - auto threshold = rewriter.create(loc, thresholdMemRef, ValueRange{}); + auto threshold = + rewriter.create(loc, thresholdMemRef, ValueRange{}); // Compare a[i] <= threshold - auto cmp1 = rewriter.create(loc, arith::CmpFPredicate::OLE, inputX, threshold); - + auto cmp1 = rewriter.create(loc, arith::CmpFPredicate::OLE, + inputX, threshold); + // Compare a[i] >= -threshold auto negThreshold = rewriter.create(loc, threshold); - auto cmp2 = rewriter.create(loc, arith::CmpFPredicate::OGE, inputX, negThreshold); + auto cmp2 = rewriter.create(loc, arith::CmpFPredicate::OGE, + inputX, negThreshold); // Combine the comparisons using AND auto cmpAnd = rewriter.create(loc, cmp1, cmp2); // Use select to choose between 0 and a[i] - auto selectOp = rewriter.create(loc, cmpAnd, constant0, inputX); + auto selectOp = + rewriter.create(loc, cmpAnd, constant0, inputX); // Store the result rewriter.create(loc, selectOp, alloc, ivY); rewriter.setInsertionPointAfter(forOpY); - //debug - // forOpY->dump(); - // affine.store %cst, %alloc_10[] : memref - // %0 = affine.load %alloc_11[4] : memref<10xf64> - // affine.store %0, %alloc[0] : memref<1xf64> - + // debug + // forOpY->dump(); + // affine.store %cst, %alloc_10[] : memref + // %0 = affine.load %alloc_11[4] : memref<10xf64> + // affine.store %0, %alloc[0] : memref<1xf64> + rewriter.replaceOp(op, alloc); - + return success(); } }; - - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: HighPassFIRHammingOptimizedOp operations //===----------------------------------------------------------------------===// struct HighPassFIRHammingOptimizedOpLowering : public ConversionPattern { HighPassFIRHammingOptimizedOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::HighPassFIRHammingOptimizedOp::getOperationName(), 1, ctx) {} + : ConversionPattern( + dsp::HighPassFIRHammingOptimizedOp::getOperationName(), 1, ctx) {} LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y_highFIRHamming[n] = -1 * [wc/pi * sinc(wc * (n- (N-1)/2))] * [0.54 - 0.46 cos(2 *pi * n/N-1)], 0<= n < (N-1)/2 : - // = 1 - wc/pi , n = (N-1)/2 - // and also, y_FIRHamming[N-1-n] = y[n] ie, store at n and also at N-1-n + // Pseudo-code: + // y_highFIRHamming[n] = -1 * [wc/pi * sinc(wc * (n- (N-1)/2))] * [0.54 - + // 0.46 cos(2 *pi * n/N-1)], 0<= n < (N-1)/2 : = 1 - wc/pi , n = (N-1)/2 - // 1 loops : first from 0 <= n < (N-1)/2 - 1 - // + // and also, y_FIRHamming[N-1-n] = y[n] ie, store at n and also at N-1-n + + // 1 loops : first from 0 <= n < (N-1)/2 - 1 + // - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); - - - //first from 0 <= i < (N-1)/2 - 1 - int64_t lb = 0 ; - int64_t N = tensorType.getShape()[0]; - int64_t ub = (N-1) / 2 ; + + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); + + // first from 0 <= i < (N-1)/2 - 1 + int64_t lb = 0; + int64_t N = tensorType.getShape()[0]; + int64_t ub = (N - 1) / 2; int64_t step = 1; DEBUG_PRINT_NO_ARGS(); - HighPassFIRHammingOptimizedOpAdaptor highPassFIRHammingOptimizedOpAdaptor(operands); - //Handle middle y[mid] = wc / pi - int64_t midIndx = ub ; - Value constantIndxMid = rewriter.create(loc, midIndx); - // rewriter.create(loc, constant0, alloc, ValueRange{constantIndx0}); - Value wc = rewriter.create(loc, highPassFIRHammingOptimizedOpAdaptor.getWc(), ValueRange{}); - Value constant1 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(1)); - Value constantMinus1 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(-1)); - Value constpi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(3.14159265359)); + HighPassFIRHammingOptimizedOpAdaptor highPassFIRHammingOptimizedOpAdaptor( + operands); + // Handle middle y[mid] = wc / pi + int64_t midIndx = ub; + Value constantIndxMid = + rewriter.create(loc, midIndx); + // rewriter.create(loc, constant0, alloc, + // ValueRange{constantIndx0}); + Value wc = rewriter.create( + loc, highPassFIRHammingOptimizedOpAdaptor.getWc(), ValueRange{}); + Value constant1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + Value constantMinus1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + Value constpi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.14159265359)); Value wcByPi = rewriter.create(loc, wc, constpi); - Value OneMinusWcByPi = rewriter.create(loc, constant1, wcByPi); - rewriter.create(loc, OneMinusWcByPi, alloc, ValueRange{constantIndxMid}); - - //first from 0 <= i < (N-1)/2 - 1 - - //calculate i-(N-1)/2 - - Value Nminus1By2 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr((float) ub)); - - //calculate 0.54 - 0.46 cos(2 *pi * n/N-1) - Value constant0_54 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0.54)); - Value constant0_46 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0.46)); - Value const2pi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(6.28318530718)); - Value NMinus1 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr((float) N - 1)); - - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + Value OneMinusWcByPi = + rewriter.create(loc, constant1, wcByPi); + rewriter.create(loc, OneMinusWcByPi, alloc, + ValueRange{constantIndxMid}); + + // first from 0 <= i < (N-1)/2 - 1 + + // calculate i-(N-1)/2 + + Value Nminus1By2 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr((float)ub)); + + // calculate 0.54 - 0.46 cos(2 *pi * n/N-1) + Value constant0_54 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0.54)); + Value constant0_46 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0.46)); + Value const2pi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(6.28318530718)); + Value NMinus1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr((float)N - 1)); + + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value i = rewriter.create(loc, rewriter.getF64Type(), IndxY); + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value i = + rewriter.create(loc, rewriter.getF64Type(), IndxY); - //get sin(wc * (i - (N-1)/ 2)) + // get sin(wc * (i - (N-1)/ 2)) Value iMinusMid = rewriter.create(loc, i, Nminus1By2); - Value mulwc_iMinusMid = rewriter.create(loc, wc , iMinusMid); + Value mulwc_iMinusMid = rewriter.create(loc, wc, iMinusMid); Value GetSin = rewriter.create(loc, mulwc_iMinusMid); - + // sin(wc*(i-(N-1)/2)) / pi * (i-(N-1)/2) - Value piMuliMinusMid = rewriter.create(loc, constpi , iMinusMid); - Value GetDiv = rewriter.create(loc, GetSin ,piMuliMinusMid) ; + Value piMuliMinusMid = + rewriter.create(loc, constpi, iMinusMid); + Value GetDiv = rewriter.create(loc, GetSin, piMuliMinusMid); // [sin(wc*(i-(N-1)/2)) / pi * (i-(N-1)/2)] * [0.54-0.46 cos(2*pi*i/N-1) - //get 2*pi * k / (N -1) - Value mul2pi_k = rewriter.create(loc, const2pi , i); - Value divIndxByNMinus1 = rewriter.create(loc, mul2pi_k, NMinus1 ) ; + // get 2*pi * k / (N -1) + Value mul2pi_k = rewriter.create(loc, const2pi, i); + Value divIndxByNMinus1 = + rewriter.create(loc, mul2pi_k, NMinus1); // get cos(2*pi * k/(N-1) Value GetCos = rewriter.create(loc, divIndxByNMinus1); - Value MulCos0_46 = rewriter.create(loc, constant0_46 , GetCos); - Value Sub0_54_Cos = rewriter.create(loc, constant0_54 ,MulCos0_46) ; - - //Multiply Sub0_54_Cos and GetDiv -- sin(wc*(i-(N-1)/2)) / pi * (i-(N-1)/2) - Value MulFilterHamming = rewriter.create(loc, GetDiv , Sub0_54_Cos); - Value MulByMinus1 = rewriter.create(loc, constantMinus1 ,MulFilterHamming) ; - rewriter.create(loc, MulByMinus1, alloc, ValueRange{ivY}); - - //also , store same value at N-1-i using affine-Map - //For affine expression: #map1 = affine_map<(%arg0)[N] : (N - 1 -%arg0) + Value MulCos0_46 = + rewriter.create(loc, constant0_46, GetCos); + Value Sub0_54_Cos = + rewriter.create(loc, constant0_54, MulCos0_46); + + // Multiply Sub0_54_Cos and GetDiv -- sin(wc*(i-(N-1)/2)) / pi * (i-(N-1)/2) + Value MulFilterHamming = + rewriter.create(loc, GetDiv, Sub0_54_Cos); + Value MulByMinus1 = + rewriter.create(loc, constantMinus1, MulFilterHamming); + rewriter.create(loc, MulByMinus1, alloc, ValueRange{ivY}); + + // also , store same value at N-1-i using affine-Map + // For affine expression: #map1 = affine_map<(%arg0)[N] : (N - 1 -%arg0) AffineExpr d0, s0; bindDims(rewriter.getContext(), d0); bindSymbols(rewriter.getContext(), s0); - //calulate N - 1 - i - AffineExpr ExprForNMinus1minusI = s0 - d0 ; - AffineMap addMapForNMinus1minusI = AffineMap::get(1, 1, ExprForNMinus1minusI); - - //store at N-1-i index , result - Value constantNMinus1Indx = rewriter.create(loc, N -1); - rewriter.create(loc, MulByMinus1, alloc, addMapForNMinus1minusI, - ValueRange{ivY,constantNMinus1Indx}); + // calulate N - 1 - i + AffineExpr ExprForNMinus1minusI = s0 - d0; + AffineMap addMapForNMinus1minusI = + AffineMap::get(1, 1, ExprForNMinus1minusI); + + // store at N-1-i index , result + Value constantNMinus1Indx = + rewriter.create(loc, N - 1); + rewriter.create(loc, MulByMinus1, alloc, + addMapForNMinus1minusI, + ValueRange{ivY, constantNMinus1Indx}); rewriter.setInsertionPointAfter(forOpY); - //debug - // forOpX->dump(); - // forOpY->dump(); - + // debug + // forOpX->dump(); + // forOpY->dump(); // affine.for %arg0 = 0 to 3 { // %12 = arith.index_castui %arg0 : index to i32 @@ -3272,11 +3461,10 @@ struct HighPassFIRHammingOptimizedOpLowering : public ConversionPattern { // affine.store %25, %alloc[-%arg0 + 6] : memref<7xf64> // } - - // } - // } + // } + // } rewriter.replaceOp(op, alloc); - + return success(); } }; @@ -3287,203 +3475,222 @@ struct HighPassFIRHammingOptimizedOpLowering : public ConversionPattern { struct FIRFilterHammingOptimizedOpLowering : public ConversionPattern { FIRFilterHammingOptimizedOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::FIRFilterHammingOptimizedOp::getOperationName(), 1, ctx) {} + : ConversionPattern(dsp::FIRFilterHammingOptimizedOp::getOperationName(), + 1, ctx) {} LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y_FIRHamming[n] = [wc/pi * sinc(wc * (n- (N-1)/2))] * [0.54 - 0.46 cos(2 *pi * n/N-1)], 0<= n < (N-1)/2 : - // = wc/pi * 1 , n = (N-1)/2 - // and also, y_FIRHamming[N-1-n] = y[n] ie, store at n and also at N-1-n + // Pseudo-code: + // y_FIRHamming[n] = [wc/pi * sinc(wc * (n- (N-1)/2))] * [0.54 - 0.46 + // cos(2 *pi * n/N-1)], 0<= n < (N-1)/2 : + // = wc/pi * 1 , n = (N-1)/2 + + // and also, y_FIRHamming[N-1-n] = y[n] ie, store at n and also at N-1-n + + // 1 loops : first from 0 <= n < (N-1)/2 - 1 + // - // 1 loops : first from 0 <= n < (N-1)/2 - 1 - // + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); - - - //first from 0 <= i < (N-1)/2 - 1 - int64_t lb = 0 ; - int64_t N = tensorType.getShape()[0]; - int64_t ub = (N-1) / 2 ; + + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); + + // first from 0 <= i < (N-1)/2 - 1 + int64_t lb = 0; + int64_t N = tensorType.getShape()[0]; + int64_t ub = (N - 1) / 2; int64_t step = 1; DEBUG_PRINT_NO_ARGS(); - FIRFilterHammingOptimizedOpAdaptor firFilterHammingOptimizedOpAdaptor(operands); - //Handle middle y[mid] = wc / pi - int64_t midIndx = ub ; - Value constantIndxMid = rewriter.create(loc, midIndx); - // rewriter.create(loc, constant0, alloc, ValueRange{constantIndx0}); - Value wc = rewriter.create(loc, firFilterHammingOptimizedOpAdaptor.getWc(), ValueRange{}); - - Value constpi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(3.14159265359)); + FIRFilterHammingOptimizedOpAdaptor firFilterHammingOptimizedOpAdaptor( + operands); + // Handle middle y[mid] = wc / pi + int64_t midIndx = ub; + Value constantIndxMid = + rewriter.create(loc, midIndx); + // rewriter.create(loc, constant0, alloc, + // ValueRange{constantIndx0}); + Value wc = rewriter.create( + loc, firFilterHammingOptimizedOpAdaptor.getWc(), ValueRange{}); + + Value constpi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.14159265359)); Value wcByPi = rewriter.create(loc, wc, constpi); - rewriter.create(loc, wcByPi, alloc, ValueRange{constantIndxMid}); + rewriter.create(loc, wcByPi, alloc, + ValueRange{constantIndxMid}); - //first from 0 <= i < (N-1)/2 - 1 + // first from 0 <= i < (N-1)/2 - 1 - //calculate i-(N-1)/2 + // calculate i-(N-1)/2 - Value Nminus1By2 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr((float) ub)); - - //calculate 0.54 - 0.46 cos(2 *pi * n/N-1) - Value constant0_54 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0.54)); - Value constant0_46 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0.46)); - Value const2pi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(6.28318530718)); - Value NMinus1 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr((float) N - 1)); + Value Nminus1By2 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr((float)ub)); - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + // calculate 0.54 - 0.46 cos(2 *pi * n/N-1) + Value constant0_54 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0.54)); + Value constant0_46 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0.46)); + Value const2pi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(6.28318530718)); + Value NMinus1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr((float)N - 1)); + + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value i = rewriter.create(loc, rewriter.getF64Type(), IndxY); + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value i = + rewriter.create(loc, rewriter.getF64Type(), IndxY); - //get sin(wc * (i - (N-1)/ 2)) + // get sin(wc * (i - (N-1)/ 2)) Value iMinusMid = rewriter.create(loc, i, Nminus1By2); - Value mulwc_iMinusMid = rewriter.create(loc, wc , iMinusMid); + Value mulwc_iMinusMid = rewriter.create(loc, wc, iMinusMid); Value GetSin = rewriter.create(loc, mulwc_iMinusMid); - + // sin(wc*(i-(N-1)/2)) / pi * (i-(N-1)/2) - Value piMuliMinusMid = rewriter.create(loc, constpi , iMinusMid); - Value GetDiv = rewriter.create(loc, GetSin ,piMuliMinusMid) ; + Value piMuliMinusMid = + rewriter.create(loc, constpi, iMinusMid); + Value GetDiv = rewriter.create(loc, GetSin, piMuliMinusMid); // [sin(wc*(i-(N-1)/2)) / pi * (i-(N-1)/2)] * [0.54-0.46 cos(2*pi*i/N-1) - //get 2*pi * k / (N -1) - Value mul2pi_k = rewriter.create(loc, const2pi , i); - Value divIndxByNMinus1 = rewriter.create(loc, mul2pi_k, NMinus1 ) ; + // get 2*pi * k / (N -1) + Value mul2pi_k = rewriter.create(loc, const2pi, i); + Value divIndxByNMinus1 = + rewriter.create(loc, mul2pi_k, NMinus1); // get cos(2*pi * k/(N-1) Value GetCos = rewriter.create(loc, divIndxByNMinus1); - Value MulCos0_46 = rewriter.create(loc, constant0_46 , GetCos); - Value Sub0_54_Cos = rewriter.create(loc, constant0_54 ,MulCos0_46) ; - - //Multiply Sub0_54_Cos and GetDiv -- sin(wc*(i-(N-1)/2)) / pi * (i-(N-1)/2) - Value MulFilterHamming = rewriter.create(loc, GetDiv , Sub0_54_Cos); - rewriter.create(loc, MulFilterHamming, alloc, ValueRange{ivY}); - - //also , store same value at N-1-i using affine-Map - //For affine expression: #map1 = affine_map<(%arg0)[N] : (N - 1 -%arg0) + Value MulCos0_46 = + rewriter.create(loc, constant0_46, GetCos); + Value Sub0_54_Cos = + rewriter.create(loc, constant0_54, MulCos0_46); + + // Multiply Sub0_54_Cos and GetDiv -- sin(wc*(i-(N-1)/2)) / pi * (i-(N-1)/2) + Value MulFilterHamming = + rewriter.create(loc, GetDiv, Sub0_54_Cos); + rewriter.create(loc, MulFilterHamming, alloc, + ValueRange{ivY}); + + // also , store same value at N-1-i using affine-Map + // For affine expression: #map1 = affine_map<(%arg0)[N] : (N - 1 -%arg0) AffineExpr d0, s0; bindDims(rewriter.getContext(), d0); bindSymbols(rewriter.getContext(), s0); - //calulate N - 1 - i - AffineExpr ExprForNMinus1minusI = s0 - d0 ; - AffineMap addMapForNMinus1minusI = AffineMap::get(1, 1, ExprForNMinus1minusI); - - //store at N-1-i index , result - Value constantNMinus1Indx = rewriter.create(loc, N -1); - rewriter.create(loc, MulFilterHamming, alloc, addMapForNMinus1minusI, - ValueRange{ivY,constantNMinus1Indx}); + // calulate N - 1 - i + AffineExpr ExprForNMinus1minusI = s0 - d0; + AffineMap addMapForNMinus1minusI = + AffineMap::get(1, 1, ExprForNMinus1minusI); + + // store at N-1-i index , result + Value constantNMinus1Indx = + rewriter.create(loc, N - 1); + rewriter.create(loc, MulFilterHamming, alloc, + addMapForNMinus1minusI, + ValueRange{ivY, constantNMinus1Indx}); rewriter.setInsertionPointAfter(forOpY); - //debug - // forOpX->dump(); - // forOpY->dump(); - + // debug + // forOpX->dump(); + // forOpY->dump(); + + // %cst = arith.constant 6.2831853071800001 : f64 + // %cst_0 = arith.constant 4.600000e-01 : f64 + // %cst_1 = arith.constant 5.400000e-01 : f64 + // %cst_2 = arith.constant 4.000000e+00 : f64 + // %alloc = memref.alloc() : memref<4xf64> + // %alloc_3 = memref.alloc() : memref + // affine.store %cst_2, %alloc_3[] : memref + // affine.for %arg0 = 0 to 4 { + // %0 = arith.index_castui %arg0 : index to i32 + // %1 = arith.uitofp %0 : i32 to f64 + // %2 = arith.mulf %1, %cst : f64 + // %3 = arith.divf %2, %cst_2 : f64 + // %4 = math.cos %3 : f64 + // %5 = arith.mulf %4, %cst_0 : f64 + // %6 = arith.subf %cst_1, %5 : f64 + // affine.store %6, %alloc[%arg0] : memref<4xf64> + // } - // %cst = arith.constant 6.2831853071800001 : f64 - // %cst_0 = arith.constant 4.600000e-01 : f64 - // %cst_1 = arith.constant 5.400000e-01 : f64 - // %cst_2 = arith.constant 4.000000e+00 : f64 - // %alloc = memref.alloc() : memref<4xf64> - // %alloc_3 = memref.alloc() : memref - // affine.store %cst_2, %alloc_3[] : memref - // affine.for %arg0 = 0 to 4 { - // %0 = arith.index_castui %arg0 : index to i32 - // %1 = arith.uitofp %0 : i32 to f64 - // %2 = arith.mulf %1, %cst : f64 - // %3 = arith.divf %2, %cst_2 : f64 - // %4 = math.cos %3 : f64 - // %5 = arith.mulf %4, %cst_0 : f64 - // %6 = arith.subf %cst_1, %5 : f64 - // affine.store %6, %alloc[%arg0] : memref<4xf64> - // } - - - // } - // } + // } + // } rewriter.replaceOp(op, alloc); - + return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: GetRangeOfVectorOp operations //===----------------------------------------------------------------------===// struct GetRangeOfVectorOpLowering : public ConversionPattern { GetRangeOfVectorOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::GetRangeOfVectorOp::getOperationName(), 1, ctx) {} + : ConversionPattern(dsp::GetRangeOfVectorOp::getOperationName(), 1, ctx) { + } LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y[0] = first: - // y[i] = y[i-1] + step for 1<=i((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + + // Pseudo-code: + // y[0] = first: + // y[i] = y[i-1] + step for 1<=i((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); GetRangeOfVectorOpAdaptor getRangeOfVectorOpOpAdaptor(operands); Value GetValueAtIndx2ndArg = op->getOperand(0); - dsp::ConstantOp constantOp2ndArg = GetValueAtIndx2ndArg.getDefiningOp(); - DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue();; + dsp::ConstantOp constantOp2ndArg = + GetValueAtIndx2ndArg.getDefiningOp(); + DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue(); + ; auto elements = constantRhsValue.getValues(); float FirstValue = elements[0].getValueAsDouble(); - - DEBUG_PRINT_WITH_ARGS("FirstValue is" , FirstValue); + + DEBUG_PRINT_WITH_ARGS("FirstValue is", FirstValue); Value GetStepOp = op->getOperand(2); - dsp::ConstantOp constantOp3rdArg = GetStepOp.getDefiningOp(); - DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue();; + dsp::ConstantOp constantOp3rdArg = + GetStepOp.getDefiningOp(); + DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue(); + ; auto elements1 = constant3rdValue.getValues(); float StepValue = elements1[0].getValueAsDouble(); - //first from 1 <= i < N - int64_t lb = 1 ; - int64_t ub = tensorType.getShape()[0]; + // first from 1 <= i < N + int64_t lb = 1; + int64_t ub = tensorType.getShape()[0]; // int64_t ub = (N-1) / 2 ; int64_t step = 1; @@ -3492,58 +3699,58 @@ struct GetRangeOfVectorOpLowering : public ConversionPattern { float valAtIndxI = FirstValue; Value constantIndx0 = rewriter.create(loc, 0); - Value constantFirst = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(valAtIndxI)); - Value constantStep = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(StepValue)); + Value constantFirst = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(valAtIndxI)); + Value constantStep = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(StepValue)); - rewriter.create(loc, constantFirst, alloc, ValueRange{constantIndx0}); + rewriter.create(loc, constantFirst, alloc, + ValueRange{constantIndx0}); - //loop from 1 <= i < N + // loop from 1 <= i < N - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step, ValueRange{constantFirst}); + affine::AffineForOp forOpY = rewriter.create( + loc, lb, ub, step, ValueRange{constantFirst}); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //Use iter_arg for taking prev_val - //Get iter_arg - auto getIterArg = forOpY.getBody()->getArgument(1); + // Use iter_arg for taking prev_val + // Get iter_arg + auto getIterArg = forOpY.getBody()->getArgument(1); // getIterArg.dump(); - Value sumNext = rewriter.create(loc, getIterArg,constantStep ); - rewriter.create(loc, sumNext, alloc, ValueRange{ivY}); + Value sumNext = + rewriter.create(loc, getIterArg, constantStep); + rewriter.create(loc, sumNext, alloc, ValueRange{ivY}); // llvm::errs() << "LINE " << __LINE__ << " file= " << __FILE__ << "\n" ; - rewriter.create(loc, ValueRange{sumNext} ); + rewriter.create(loc, ValueRange{sumNext}); rewriter.setInsertionPointAfter(forOpY); + // debug + // forOpX->dump(); + // forOpY->dump(); + + // %cst = arith.constant 6.2831853071800001 : f64 + // %cst_0 = arith.constant 4.600000e-01 : f64 + // %cst_1 = arith.constant 5.400000e-01 : f64 + // %cst_2 = arith.constant 4.000000e+00 : f64 + // %alloc = memref.alloc() : memref<4xf64> + // %alloc_3 = memref.alloc() : memref + // affine.store %cst_2, %alloc_3[] : memref + // affine.for %arg0 = 0 to 4 { + // %0 = arith.index_castui %arg0 : index to i32 + // %1 = arith.uitofp %0 : i32 to f64 + // %2 = arith.mulf %1, %cst : f64 + // %3 = arith.divf %2, %cst_2 : f64 + // %4 = math.cos %3 : f64 + // %5 = arith.mulf %4, %cst_0 : f64 + // %6 = arith.subf %cst_1, %5 : f64 + // affine.store %6, %alloc[%arg0] : memref<4xf64> + // } - //debug - // forOpX->dump(); - // forOpY->dump(); - - - // %cst = arith.constant 6.2831853071800001 : f64 - // %cst_0 = arith.constant 4.600000e-01 : f64 - // %cst_1 = arith.constant 5.400000e-01 : f64 - // %cst_2 = arith.constant 4.000000e+00 : f64 - // %alloc = memref.alloc() : memref<4xf64> - // %alloc_3 = memref.alloc() : memref - // affine.store %cst_2, %alloc_3[] : memref - // affine.for %arg0 = 0 to 4 { - // %0 = arith.index_castui %arg0 : index to i32 - // %1 = arith.uitofp %0 : i32 to f64 - // %2 = arith.mulf %1, %cst : f64 - // %3 = arith.divf %2, %cst_2 : f64 - // %4 = math.cos %3 : f64 - // %5 = arith.mulf %4, %cst_0 : f64 - // %6 = arith.subf %cst_1, %5 : f64 - // affine.store %6, %alloc[%arg0] : memref<4xf64> - // } - - - // } - // } + // } + // } rewriter.replaceOp(op, alloc); - + return success(); } }; @@ -3554,283 +3761,309 @@ struct GetRangeOfVectorOpLowering : public ConversionPattern { struct HighPassFIRFilterOpLowering : public ConversionPattern { HighPassFIRFilterOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::HighPassFIRFilterOp::getOperationName(), 1, ctx) {} + : ConversionPattern(dsp::HighPassFIRFilterOp::getOperationName(), 1, + ctx) {} LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y_lpf[n] = wc/pi * sinc(wc * (n- (N-1)/2)) , n!= (N-1)/2 : - // = wc/pi , n = (N-1)/2 - // y_hpf[n] = dirac(n- (N-1)/2) - y_lpf[n] = -1 * wc/pi * sinc(wc * (n- (N-1)/2)) , n!= (N-1)/2 : - // = 1 - wc/pi , n = (N-1)/2 - - // 2 loops : first from 0 <= n <= (N-1)/2 - 1 - // 2nd from (N-1)/2 +1 <= n < N - - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + + // Pseudo-code: + // y_lpf[n] = wc/pi * sinc(wc * (n- (N-1)/2)) , n!= (N-1)/2 : + // = wc/pi , n = (N-1)/2 + // y_hpf[n] = dirac(n- (N-1)/2) - y_lpf[n] = -1 * wc/pi * sinc(wc * (n- + // (N-1)/2)) , n!= (N-1)/2 : + // = 1 - wc/pi , n = (N-1)/2 + + // 2 loops : first from 0 <= n <= (N-1)/2 - 1 + // 2nd from (N-1)/2 +1 <= n < N + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); - - - //first from 0 <= i <= (N-1)/2 - 1 - int64_t lb = 0 ; - int64_t N = tensorType.getShape()[0]; - int64_t ub = (N-1) / 2 ; + + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); + + // first from 0 <= i <= (N-1)/2 - 1 + int64_t lb = 0; + int64_t N = tensorType.getShape()[0]; + int64_t ub = (N - 1) / 2; int64_t step = 1; DEBUG_PRINT_NO_ARGS(); HighPassFIRFilterOpAdaptor highPassfirFilterOpAdaptor(operands); - //Handle middle y[mid] = wc / pi - int64_t midIndx = ub ; - Value constantIndxMid = rewriter.create(loc, midIndx); - // rewriter.create(loc, constant0, alloc, ValueRange{constantIndx0}); - Value constant1 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(1)); - Value constantMinus1 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(-1)); - - Value wc = rewriter.create(loc, highPassfirFilterOpAdaptor.getWc(), ValueRange{}); - - Value constpi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(3.14159265359)); + // Handle middle y[mid] = wc / pi + int64_t midIndx = ub; + Value constantIndxMid = + rewriter.create(loc, midIndx); + // rewriter.create(loc, constant0, alloc, + // ValueRange{constantIndx0}); + Value constant1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + Value constantMinus1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + + Value wc = rewriter.create( + loc, highPassfirFilterOpAdaptor.getWc(), ValueRange{}); + + Value constpi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.14159265359)); Value wcByPi = rewriter.create(loc, wc, constpi); - Value OneMinusWcByPi = rewriter.create(loc, constant1, wcByPi); - rewriter.create(loc, OneMinusWcByPi, alloc, ValueRange{constantIndxMid}); - - //first from 0 <= i <= (N-1)/2 - 1 - - //calculate i-(N-1)/2 - Value Nminus1By2 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr((float) ub)); - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + Value OneMinusWcByPi = + rewriter.create(loc, constant1, wcByPi); + rewriter.create(loc, OneMinusWcByPi, alloc, + ValueRange{constantIndxMid}); + + // first from 0 <= i <= (N-1)/2 - 1 + + // calculate i-(N-1)/2 + Value Nminus1By2 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr((float)ub)); + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value i = rewriter.create(loc, rewriter.getF64Type(), IndxY); + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value i = + rewriter.create(loc, rewriter.getF64Type(), IndxY); - //get sin(wc * (i - (N-1)/ 2)) + // get sin(wc * (i - (N-1)/ 2)) Value iMinusMid = rewriter.create(loc, i, Nminus1By2); - Value mulwc_iMinusMid = rewriter.create(loc, wc , iMinusMid); + Value mulwc_iMinusMid = rewriter.create(loc, wc, iMinusMid); Value GetSin = rewriter.create(loc, mulwc_iMinusMid); - + // get sin(wc*i) / pi * i - Value piMuliMinusMid = rewriter.create(loc, constpi , iMinusMid); - Value GetDiv = rewriter.create(loc, GetSin ,piMuliMinusMid) ; - Value MulByMinus1 = rewriter.create(loc, constantMinus1 ,GetDiv) ; - rewriter.create(loc, MulByMinus1, alloc, ValueRange{ivY}); + Value piMuliMinusMid = + rewriter.create(loc, constpi, iMinusMid); + Value GetDiv = rewriter.create(loc, GetSin, piMuliMinusMid); + Value MulByMinus1 = + rewriter.create(loc, constantMinus1, GetDiv); + rewriter.create(loc, MulByMinus1, alloc, ValueRange{ivY}); // llvm::errs() << "LINE " << __LINE__ << " file= " << __FILE__ << "\n" ; rewriter.setInsertionPointAfter(forOpY); - //2nd loop from (N-1)/2 + 1 <= i < N - lb = ub + 1 ; - ub = N ; + // 2nd loop from (N-1)/2 + 1 <= i < N + lb = ub + 1; + ub = N; - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv1 = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); - //convert index to f64 - Value Indx1 = rewriter.create(loc, rewriter.getIntegerType(32), iv1); - Value i1 = rewriter.create(loc, rewriter.getF64Type(), Indx1); + // convert index to f64 + Value Indx1 = rewriter.create( + loc, rewriter.getIntegerType(32), iv1); + Value i1 = + rewriter.create(loc, rewriter.getF64Type(), Indx1); - //get sin(wc * (i1 - (N-1)/ 2)) + // get sin(wc * (i1 - (N-1)/ 2)) Value iMinusMid1 = rewriter.create(loc, i1, Nminus1By2); - Value mulwc_iMinusMid1 = rewriter.create(loc, wc , iMinusMid1); + Value mulwc_iMinusMid1 = + rewriter.create(loc, wc, iMinusMid1); Value GetSin1 = rewriter.create(loc, mulwc_iMinusMid1); - //get sin(i1 - (N-1)/ 2) / (i1 - (N-1)/ 2) * pi - // get sin(wc*i1) / pi * i1 + // get sin(i1 - (N-1)/ 2) / (i1 - (N-1)/ 2) * pi + // get sin(wc*i1) / pi * i1 - Value piMuliMinusMid1 = rewriter.create(loc, constpi , iMinusMid1); - Value GetDiv1 = rewriter.create(loc, GetSin1 ,piMuliMinusMid1) ; + Value piMuliMinusMid1 = + rewriter.create(loc, constpi, iMinusMid1); + Value GetDiv1 = + rewriter.create(loc, GetSin1, piMuliMinusMid1); - Value GetDiv1MulNeg1 = rewriter.create(loc, constantMinus1 ,GetDiv1) ; + Value GetDiv1MulNeg1 = + rewriter.create(loc, constantMinus1, GetDiv1); - rewriter.create(loc, GetDiv1MulNeg1, alloc, ValueRange{iv1}); + rewriter.create(loc, GetDiv1MulNeg1, alloc, ValueRange{iv1}); // llvm::errs() << "LINE " << __LINE__ << " file= " << __FILE__ << "\n" ; rewriter.setInsertionPointAfter(forOp1); - //debug - // forOpX->dump(); - // forOpY->dump(); - + // debug + // forOpX->dump(); + // forOpY->dump(); + + // %cst = arith.constant 6.2831853071800001 : f64 + // %cst_0 = arith.constant 4.600000e-01 : f64 + // %cst_1 = arith.constant 5.400000e-01 : f64 + // %cst_2 = arith.constant 4.000000e+00 : f64 + // %alloc = memref.alloc() : memref<4xf64> + // %alloc_3 = memref.alloc() : memref + // affine.store %cst_2, %alloc_3[] : memref + // affine.for %arg0 = 0 to 4 { + // %0 = arith.index_castui %arg0 : index to i32 + // %1 = arith.uitofp %0 : i32 to f64 + // %2 = arith.mulf %1, %cst : f64 + // %3 = arith.divf %2, %cst_2 : f64 + // %4 = math.cos %3 : f64 + // %5 = arith.mulf %4, %cst_0 : f64 + // %6 = arith.subf %cst_1, %5 : f64 + // affine.store %6, %alloc[%arg0] : memref<4xf64> + // } - // %cst = arith.constant 6.2831853071800001 : f64 - // %cst_0 = arith.constant 4.600000e-01 : f64 - // %cst_1 = arith.constant 5.400000e-01 : f64 - // %cst_2 = arith.constant 4.000000e+00 : f64 - // %alloc = memref.alloc() : memref<4xf64> - // %alloc_3 = memref.alloc() : memref - // affine.store %cst_2, %alloc_3[] : memref - // affine.for %arg0 = 0 to 4 { - // %0 = arith.index_castui %arg0 : index to i32 - // %1 = arith.uitofp %0 : i32 to f64 - // %2 = arith.mulf %1, %cst : f64 - // %3 = arith.divf %2, %cst_2 : f64 - // %4 = math.cos %3 : f64 - // %5 = arith.mulf %4, %cst_0 : f64 - // %6 = arith.subf %cst_1, %5 : f64 - // affine.store %6, %alloc[%arg0] : memref<4xf64> - // } - - - // } - // } + // } + // } rewriter.replaceOp(op, alloc); - + return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: LowPassFIRFilterOp operations //===----------------------------------------------------------------------===// struct LowPassFIRFilterOpLowering : public ConversionPattern { LowPassFIRFilterOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::LowPassFIRFilterOp::getOperationName(), 1, ctx) {} + : ConversionPattern(dsp::LowPassFIRFilterOp::getOperationName(), 1, ctx) { + } LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y_lpf[n] = wc/pi * sinc(wc * (n- (N-1)/2)) , n!= (N-1)/2 : - // = wc/pi , n = (N-1)/2 - - // 2 loops : first from 0 <= n <= (N-1)/2 - 1 - // 2nd from (N-1)/2 +1 <= n < N - - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + + // Pseudo-code: + // y_lpf[n] = wc/pi * sinc(wc * (n- (N-1)/2)) , n!= (N-1)/2 : + // = wc/pi , n = (N-1)/2 + + // 2 loops : first from 0 <= n <= (N-1)/2 - 1 + // 2nd from (N-1)/2 +1 <= n < N + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); - - - //first from 0 <= i <= (N-1)/2 - 1 - int64_t lb = 0 ; - int64_t N = tensorType.getShape()[0]; - int64_t ub = (N-1) / 2 ; + + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); + + // first from 0 <= i <= (N-1)/2 - 1 + int64_t lb = 0; + int64_t N = tensorType.getShape()[0]; + int64_t ub = (N - 1) / 2; int64_t step = 1; DEBUG_PRINT_NO_ARGS(); LowPassFIRFilterOpAdaptor lowPassfirFilterOpAdaptor(operands); - //Handle middle y[mid] = wc / pi - int64_t midIndx = ub ; - Value constantIndxMid = rewriter.create(loc, midIndx); - // rewriter.create(loc, constant0, alloc, ValueRange{constantIndx0}); - Value wc = rewriter.create(loc, lowPassfirFilterOpAdaptor.getWc(), ValueRange{}); - - Value constpi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(3.14159265359)); + // Handle middle y[mid] = wc / pi + int64_t midIndx = ub; + Value constantIndxMid = + rewriter.create(loc, midIndx); + // rewriter.create(loc, constant0, alloc, + // ValueRange{constantIndx0}); + Value wc = rewriter.create( + loc, lowPassfirFilterOpAdaptor.getWc(), ValueRange{}); + + Value constpi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.14159265359)); Value wcByPi = rewriter.create(loc, wc, constpi); - rewriter.create(loc, wcByPi, alloc, ValueRange{constantIndxMid}); + rewriter.create(loc, wcByPi, alloc, + ValueRange{constantIndxMid}); - //first from 0 <= i <= (N-1)/2 - 1 + // first from 0 <= i <= (N-1)/2 - 1 - //calculate i-(N-1)/2 - Value Nminus1By2 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr((float) ub)); - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + // calculate i-(N-1)/2 + Value Nminus1By2 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr((float)ub)); + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value i = rewriter.create(loc, rewriter.getF64Type(), IndxY); + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value i = + rewriter.create(loc, rewriter.getF64Type(), IndxY); - //get sin(wc * (i - (N-1)/ 2)) + // get sin(wc * (i - (N-1)/ 2)) Value iMinusMid = rewriter.create(loc, i, Nminus1By2); - Value mulwc_iMinusMid = rewriter.create(loc, wc , iMinusMid); + Value mulwc_iMinusMid = rewriter.create(loc, wc, iMinusMid); Value GetSin = rewriter.create(loc, mulwc_iMinusMid); - + // get sin(wc*i) / pi * i - Value piMuliMinusMid = rewriter.create(loc, constpi , iMinusMid); - Value GetDiv = rewriter.create(loc, GetSin ,piMuliMinusMid) ; - rewriter.create(loc, GetDiv, alloc, ValueRange{ivY}); + Value piMuliMinusMid = + rewriter.create(loc, constpi, iMinusMid); + Value GetDiv = rewriter.create(loc, GetSin, piMuliMinusMid); + rewriter.create(loc, GetDiv, alloc, ValueRange{ivY}); // llvm::errs() << "LINE " << __LINE__ << " file= " << __FILE__ << "\n" ; rewriter.setInsertionPointAfter(forOpY); - //2nd loop from (N-1)/2 + 1 <= i < N - lb = ub + 1 ; - ub = N ; + // 2nd loop from (N-1)/2 + 1 <= i < N + lb = ub + 1; + ub = N; - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv1 = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); - //convert index to f64 - Value Indx1 = rewriter.create(loc, rewriter.getIntegerType(32), iv1); - Value i1 = rewriter.create(loc, rewriter.getF64Type(), Indx1); + // convert index to f64 + Value Indx1 = rewriter.create( + loc, rewriter.getIntegerType(32), iv1); + Value i1 = + rewriter.create(loc, rewriter.getF64Type(), Indx1); - //get sin(wc * (i1 - (N-1)/ 2)) + // get sin(wc * (i1 - (N-1)/ 2)) Value iMinusMid1 = rewriter.create(loc, i1, Nminus1By2); - Value mulwc_iMinusMid1 = rewriter.create(loc, wc , iMinusMid1); + Value mulwc_iMinusMid1 = + rewriter.create(loc, wc, iMinusMid1); Value GetSin1 = rewriter.create(loc, mulwc_iMinusMid1); - //get sin(i1 - (N-1)/ 2) / (i1 - (N-1)/ 2) * pi - // get sin(wc*i1) / pi * i1 + // get sin(i1 - (N-1)/ 2) / (i1 - (N-1)/ 2) * pi + // get sin(wc*i1) / pi * i1 - Value piMuliMinusMid1 = rewriter.create(loc, constpi , iMinusMid1); - Value GetDiv1 = rewriter.create(loc, GetSin1 ,piMuliMinusMid1) ; - rewriter.create(loc, GetDiv1, alloc, ValueRange{iv1}); + Value piMuliMinusMid1 = + rewriter.create(loc, constpi, iMinusMid1); + Value GetDiv1 = + rewriter.create(loc, GetSin1, piMuliMinusMid1); + rewriter.create(loc, GetDiv1, alloc, ValueRange{iv1}); // llvm::errs() << "LINE " << __LINE__ << " file= " << __FILE__ << "\n" ; rewriter.setInsertionPointAfter(forOp1); - //debug - // forOpX->dump(); - // forOpY->dump(); - + // debug + // forOpX->dump(); + // forOpY->dump(); + + // %cst = arith.constant 6.2831853071800001 : f64 + // %cst_0 = arith.constant 4.600000e-01 : f64 + // %cst_1 = arith.constant 5.400000e-01 : f64 + // %cst_2 = arith.constant 4.000000e+00 : f64 + // %alloc = memref.alloc() : memref<4xf64> + // %alloc_3 = memref.alloc() : memref + // affine.store %cst_2, %alloc_3[] : memref + // affine.for %arg0 = 0 to 4 { + // %0 = arith.index_castui %arg0 : index to i32 + // %1 = arith.uitofp %0 : i32 to f64 + // %2 = arith.mulf %1, %cst : f64 + // %3 = arith.divf %2, %cst_2 : f64 + // %4 = math.cos %3 : f64 + // %5 = arith.mulf %4, %cst_0 : f64 + // %6 = arith.subf %cst_1, %5 : f64 + // affine.store %6, %alloc[%arg0] : memref<4xf64> + // } - // %cst = arith.constant 6.2831853071800001 : f64 - // %cst_0 = arith.constant 4.600000e-01 : f64 - // %cst_1 = arith.constant 5.400000e-01 : f64 - // %cst_2 = arith.constant 4.000000e+00 : f64 - // %alloc = memref.alloc() : memref<4xf64> - // %alloc_3 = memref.alloc() : memref - // affine.store %cst_2, %alloc_3[] : memref - // affine.for %arg0 = 0 to 4 { - // %0 = arith.index_castui %arg0 : index to i32 - // %1 = arith.uitofp %0 : i32 to f64 - // %2 = arith.mulf %1, %cst : f64 - // %3 = arith.divf %2, %cst_2 : f64 - // %4 = math.cos %3 : f64 - // %5 = arith.mulf %4, %cst_0 : f64 - // %6 = arith.subf %cst_1, %5 : f64 - // affine.store %6, %alloc[%arg0] : memref<4xf64> - // } - - - // } - // } + // } + // } rewriter.replaceOp(op, alloc); - + return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: SetElemAtIndx operations //===----------------------------------------------------------------------===// @@ -3843,71 +4076,82 @@ struct SetElemAtIndxOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // output = input[index] - // replace this upsampling op with the output_mem_allocation op + // Pseudo-code: + // output = input[index] + + // replace this upsampling op with the output_mem_allocation op DEBUG_PRINT_NO_ARGS(); - //output for result type + // output for result type SetElemAtIndxOpAdaptor setElemAtIndxAdaptor(operands); - auto tensorType = llvm::cast((*op->result_type_begin())); - // auto tensorType = llvm::cast(setElemAtIndxAdaptor.getInput()); - //iterate to result1 --not needed for now but for future reference - - //allocation & deallocation for the result of this operation + auto tensorType = llvm::cast((*op->result_type_begin())); + // auto tensorType = + // llvm::cast(setElemAtIndxAdaptor.getInput()); + // iterate to result1 --not needed for now but for future reference + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //For loop -- iterate from 1 to last - // int64_t lb = 0 ; - // int64_t ub = tensorType.getShape()[0]; - // int64_t step = 1; - // affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); - // auto ivY = forOpY.getInductionVar(); - // rewriter.setInsertionPointToStart(forOpY.getBody()); + // For loop -- iterate from 1 to last + // int64_t lb = 0 ; + // int64_t ub = tensorType.getShape()[0]; + // int64_t step = 1; + // affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, + // step); auto ivY = forOpY.getInductionVar(); + // rewriter.setInsertionPointToStart(forOpY.getBody()); - // Value inputX = rewriter.create(loc, setElemAtIndxAdaptor.getInput(), ValueRange{ivY}); + // Value inputX = rewriter.create(loc, + // setElemAtIndxAdaptor.getInput(), ValueRange{ivY}); // rewriter.create(loc, inputX, alloc, ValueRange{ivY}); // rewriter.setInsertionPointAfter(forOpY); DEBUG_PRINT_WITH_ARGS("\nCheck for index --here"); - //load from X, using 2nd operand as index + // load from X, using 2nd operand as index - // Value GetValueAtIndx2ndArg = setElemAtIndxAdaptor.getIndx(); // getOperand(1); + // Value GetValueAtIndx2ndArg = setElemAtIndxAdaptor.getIndx(); // + // getOperand(1); DEBUG_PRINT_NO_ARGS(); Value GetValueAtIndx2ndArg = op->getOperand(1); - dsp::ConstantOp constantOp2ndArg = GetValueAtIndx2ndArg.getDefiningOp(); - DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue();; + dsp::ConstantOp constantOp2ndArg = + GetValueAtIndx2ndArg.getDefiningOp(); + DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue(); + ; auto elements = constantRhsValue.getValues(); float SecondValue = elements[0].getValueAsDouble(); - int SecondValueInt = (int64_t) SecondValue; - DEBUG_PRINT_WITH_ARGS("Indx is" , SecondValueInt); + int SecondValueInt = (int64_t)SecondValue; + DEBUG_PRINT_WITH_ARGS("Indx is", SecondValueInt); - Value constantIndx2Indx = rewriter.create(loc, SecondValueInt); + Value constantIndx2Indx = + rewriter.create(loc, SecondValueInt); Value constantIndx0 = rewriter.create(loc, 0); - // Value constant0 = rewriter.create(loc, rewriter.getF64Type(), + // Value constant0 = rewriter.create(loc, + // rewriter.getF64Type(), // rewriter.getF64FloatAttr(15)); - // Value ValToStore = setElemAtIndxAdaptor.getVal(); + // Value ValToStore = setElemAtIndxAdaptor.getVal(); // Value ValToStore = op->getOperand(2); - Value ValToStore = rewriter.create(loc, setElemAtIndxAdaptor.getVal(), ValueRange{constantIndx0}); - // Value ValToStore = rewriter.create(loc, setElemAtIndxAdaptor.getVal(), ValueRange{}); - - // rewriter.create(loc, constant0, alloc, ValueRange{constantIndx2Indx}); - rewriter.create(loc, ValToStore, setElemAtIndxAdaptor.getInput(), ValueRange{constantIndx2Indx}); + Value ValToStore = rewriter.create( + loc, setElemAtIndxAdaptor.getVal(), ValueRange{constantIndx0}); + // Value ValToStore = rewriter.create(loc, + // setElemAtIndxAdaptor.getVal(), ValueRange{}); + + // rewriter.create(loc, constant0, alloc, + // ValueRange{constantIndx2Indx}); + rewriter.create(loc, ValToStore, + setElemAtIndxAdaptor.getInput(), + ValueRange{constantIndx2Indx}); + + // debug + // forOpY->dump(); + // affine.store %cst, %alloc_10[] : memref + // %0 = affine.load %alloc_11[4] : memref<10xf64> + // affine.store %0, %alloc[0] : memref<1xf64> - - //debug - // forOpY->dump(); - // affine.store %cst, %alloc_10[] : memref - // %0 = affine.load %alloc_11[4] : memref<10xf64> - // affine.store %0, %alloc[0] : memref<1xf64> - rewriter.replaceOp(op, alloc); - + return success(); } }; @@ -3916,7 +4160,6 @@ struct SetElemAtIndxOpLowering : public ConversionPattern { // ToyToAffine RewritePatterns: GetElemAtIndx operations //===----------------------------------------------------------------------===// - struct GetElemAtIndxOpLowering : public ConversionPattern { GetElemAtIndxOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::GetElemAtIndxOp::getOperationName(), 1, ctx) {} @@ -3925,62 +4168,65 @@ struct GetElemAtIndxOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // output = input[index] - // replace this upsampling op with the output_mem_allocation op + // Pseudo-code: + // output = input[index] + + // replace this upsampling op with the output_mem_allocation op DEBUG_PRINT_NO_ARGS(); - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - //iterate to result1 --not needed for now but for future reference - - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + // iterate to result1 --not needed for now but for future reference + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); // auto memRefType2 = convertTensorToMemRef(tensorType1); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - // Value constant0 = rewriter.create(loc, rewriter.getF64Type(), + // Value constant0 = rewriter.create(loc, + // rewriter.getF64Type(), // rewriter.getF64FloatAttr(0)); - - DEBUG_PRINT_WITH_ARGS("\nCheck for index --here"); - //load from X, using 2nd operand as index + // load from X, using 2nd operand as index GetElemAtIndxOpAdaptor getElemAtIndxAdaptor(operands); - // Value GetValueAtIndx2ndArg = getElemAtIndxAdaptor.getIndx(); // getOperand(1); + // Value GetValueAtIndx2ndArg = getElemAtIndxAdaptor.getIndx(); // + // getOperand(1); DEBUG_PRINT_NO_ARGS(); Value GetValueAtIndx2ndArg = op->getOperand(1); - dsp::ConstantOp constantOp2ndArg = GetValueAtIndx2ndArg.getDefiningOp(); - DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue();; + dsp::ConstantOp constantOp2ndArg = + GetValueAtIndx2ndArg.getDefiningOp(); + DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue(); + ; auto elements = constantRhsValue.getValues(); float SecondValue = elements[0].getValueAsDouble(); - int SecondValueInt = (int64_t) SecondValue; - DEBUG_PRINT_WITH_ARGS("Indx is" , SecondValueInt); + int SecondValueInt = (int64_t)SecondValue; + DEBUG_PRINT_WITH_ARGS("Indx is", SecondValueInt); - Value constantIndx2Indx = rewriter.create(loc, SecondValueInt); + Value constantIndx2Indx = + rewriter.create(loc, SecondValueInt); Value constantIndx0 = rewriter.create(loc, 0); - - Value inputX = rewriter.create(loc, getElemAtIndxAdaptor.getInput(), ValueRange{constantIndx2Indx}); - rewriter.create(loc, inputX, alloc, ValueRange{constantIndx0}); + Value inputX = rewriter.create( + loc, getElemAtIndxAdaptor.getInput(), ValueRange{constantIndx2Indx}); + rewriter.create(loc, inputX, alloc, + ValueRange{constantIndx0}); + + // debug + // forOpX->dump(); + // forOpY->dump(); + // affine.store %cst, %alloc_10[] : memref + // %0 = affine.load %alloc_11[4] : memref<10xf64> + // affine.store %0, %alloc[0] : memref<1xf64> - //debug - // forOpX->dump(); - // forOpY->dump(); - // affine.store %cst, %alloc_10[] : memref - // %0 = affine.load %alloc_11[4] : memref<10xf64> - // affine.store %0, %alloc[0] : memref<1xf64> - rewriter.replaceOp(op, alloc); - + return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: SincOp operations //===----------------------------------------------------------------------===// @@ -3993,100 +4239,100 @@ struct SincOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y = sinc(wc * n) = [1, sin(wc)/pi , sin(2* wc)/2*pi , ... sin(n * wc)/n*pi] , 0<=n<=N - - - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + + // Pseudo-code: + // y = sinc(wc * n) = [1, sin(wc)/pi , sin(2* wc)/2*pi , ... sin(n * + // wc)/n*pi] , 0<=n<=N + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); - - - //For loop -- iterate from 1 to last - int64_t lb = 1 ; - int64_t ub = tensorType.getShape()[0]; + + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); + + // For loop -- iterate from 1 to last + int64_t lb = 1; + int64_t ub = tensorType.getShape()[0]; int64_t step = 1; DEBUG_PRINT_NO_ARGS(); - //get constants -- 0.54 & 0.46 + // get constants -- 0.54 & 0.46 Value constantIndx0 = rewriter.create(loc, 0); - // rewriter.create(loc, constant0, alloc, ValueRange{constantIndx0}); - - Value constant1 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(1)); - Value constpi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(3.14159265359)); - rewriter.create(loc, constant1, alloc, ValueRange{constantIndx0}); - - //For loop + // rewriter.create(loc, constant0, alloc, + // ValueRange{constantIndx0}); + + Value constant1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + Value constpi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.14159265359)); + rewriter.create(loc, constant1, alloc, + ValueRange{constantIndx0}); + + // For loop SincOpAdaptor sincOpAdaptor(operands); - //loop for Y - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + // loop for Y + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value i = rewriter.create(loc, rewriter.getF64Type(), IndxY); + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value i = + rewriter.create(loc, rewriter.getF64Type(), IndxY); + // get wc * i + Value wc = + rewriter.create(loc, sincOpAdaptor.getWc(), ValueRange{}); - //get wc * i - Value wc = rewriter.create(loc, sincOpAdaptor.getWc(), ValueRange{}); - - Value mulwc_i = rewriter.create(loc, wc , i); + Value mulwc_i = rewriter.create(loc, wc, i); // get sin(wc*i) / pi * i Value GetSin = rewriter.create(loc, mulwc_i); - Value piMuli = rewriter.create(loc, constpi , i); - Value GetDiv = rewriter.create(loc, GetSin ,piMuli) ; - rewriter.create(loc, GetDiv, alloc, ValueRange{ivY}); + Value piMuli = rewriter.create(loc, constpi, i); + Value GetDiv = rewriter.create(loc, GetSin, piMuli); + rewriter.create(loc, GetDiv, alloc, ValueRange{ivY}); // llvm::errs() << "LINE " << __LINE__ << " file= " << __FILE__ << "\n" ; rewriter.setInsertionPointAfter(forOpY); - //debug - // forOpX->dump(); - // forOpY->dump(); - + // debug + // forOpX->dump(); + // forOpY->dump(); + + // %cst = arith.constant 6.2831853071800001 : f64 + // %cst_0 = arith.constant 4.600000e-01 : f64 + // %cst_1 = arith.constant 5.400000e-01 : f64 + // %cst_2 = arith.constant 4.000000e+00 : f64 + // %alloc = memref.alloc() : memref<4xf64> + // %alloc_3 = memref.alloc() : memref + // affine.store %cst_2, %alloc_3[] : memref + // affine.for %arg0 = 0 to 4 { + // %0 = arith.index_castui %arg0 : index to i32 + // %1 = arith.uitofp %0 : i32 to f64 + // %2 = arith.mulf %1, %cst : f64 + // %3 = arith.divf %2, %cst_2 : f64 + // %4 = math.cos %3 : f64 + // %5 = arith.mulf %4, %cst_0 : f64 + // %6 = arith.subf %cst_1, %5 : f64 + // affine.store %6, %alloc[%arg0] : memref<4xf64> + // } - // %cst = arith.constant 6.2831853071800001 : f64 - // %cst_0 = arith.constant 4.600000e-01 : f64 - // %cst_1 = arith.constant 5.400000e-01 : f64 - // %cst_2 = arith.constant 4.000000e+00 : f64 - // %alloc = memref.alloc() : memref<4xf64> - // %alloc_3 = memref.alloc() : memref - // affine.store %cst_2, %alloc_3[] : memref - // affine.for %arg0 = 0 to 4 { - // %0 = arith.index_castui %arg0 : index to i32 - // %1 = arith.uitofp %0 : i32 to f64 - // %2 = arith.mulf %1, %cst : f64 - // %3 = arith.divf %2, %cst_2 : f64 - // %4 = math.cos %3 : f64 - // %5 = arith.mulf %4, %cst_0 : f64 - // %6 = arith.subf %cst_1, %5 : f64 - // affine.store %6, %alloc[%arg0] : memref<4xf64> - // } - - - // } - // } + // } + // } rewriter.replaceOp(op, alloc); - + return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: FFT1DImg operations //===----------------------------------------------------------------------===// - struct FFT1DImgOpLowering : public ConversionPattern { FFT1DImgOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::FFT1DImgOp::getOperationName(), 1, ctx) {} @@ -4095,171 +4341,181 @@ struct FFT1DImgOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y[k] = y_real[k] + j *y_img[k] - // y_img = sumOver_n(x[n]*sin[2*pi * k *n/N ] * -1 - //init output mem for y_real & y_img as 0 - //iterate for output from k=0 to last - //iterate for all x from n=0 to last - //perform the calculations : ie x[n] * cos[2*pi * k *n/N ] and sum and store them at y[k] - // - // replace this upsampling op with the output_mem_allocation op + + // Pseudo-code: + // y[k] = y_real[k] + j *y_img[k] + // y_img = sumOver_n(x[n]*sin[2*pi * k *n/N ] * -1 + // init output mem for y_real & y_img as 0 + // iterate for output from k=0 to last + // iterate for all x from n=0 to last + // perform the calculations : ie x[n] * cos[2*pi * k *n/N ] and sum and + // store them at y[k] + // + // replace this upsampling op with the output_mem_allocation op // DEBUG_PRINT_NO_ARGS() ; - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - //iterate to result1 --not needed for now but for future reference - // auto tensorType1 = llvm::cast(*std::next(op->result_type_begin(), 1)); + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + // iterate to result1 --not needed for now but for future reference + // auto tensorType1 = + // llvm::cast(*std::next(op->result_type_begin(), 1)); + + // DEBUG_PRINT_NO_ARGS() ; + // tensorType.getShape()[0] + // llvm::errs() << "tensorType1.getShape()[0] " << tensorType1.getShape()[0] + // << " func= " << __func__ << "\n"; - // DEBUG_PRINT_NO_ARGS() ; - //tensorType.getShape()[0] - // llvm::errs() << "tensorType1.getShape()[0] " << tensorType1.getShape()[0] << " func= " << __func__ << "\n"; - - //allocation & deallocation for the result of this operation + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); // auto memRefType2 = convertTensorToMemRef(tensorType1); auto alloc_img = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); - + // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - //For loop -- iterate from 1 to last - int64_t lb = 0 ; + // For loop -- iterate from 1 to last + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); rewriter.create(loc, constant0, alloc_img, ValueRange{iv}); rewriter.setInsertionPointAfter(forOp1); - //loop for Y - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + // loop for Y + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - - //loop for X - affine::AffineForOp forOpX = rewriter.create(loc, lb, ub, step); + // loop for X + affine::AffineForOp forOpX = + rewriter.create(loc, lb, ub, step); auto ivX = forOpX.getInductionVar(); rewriter.setInsertionPointToStart(forOpX.getBody()); - //load from X, & y1 & y2 + // load from X, & y1 & y2 FFT1DImgOpAdaptor fft1DImgAdaptor(operands); - Value inputX = rewriter.create(loc, fft1DImgAdaptor.getInput(), ValueRange{ivX}); - Value loadYImg = rewriter.create(loc, alloc_img, ValueRange{ivY}); + Value inputX = rewriter.create( + loc, fft1DImgAdaptor.getInput(), ValueRange{ivX}); + Value loadYImg = + rewriter.create(loc, alloc_img, ValueRange{ivY}); + + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value k = + rewriter.create(loc, rewriter.getF64Type(), IndxY); - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value k = rewriter.create(loc, rewriter.getF64Type(), IndxY); + Value IndxX = rewriter.create( + loc, rewriter.getIntegerType(32), ivX); + Value i = + rewriter.create(loc, rewriter.getF64Type(), IndxX); - Value IndxX = rewriter.create(loc, rewriter.getIntegerType(32), ivX); - Value i = rewriter.create(loc, rewriter.getF64Type(), IndxX); + // get 2*pi * k * i / N + Value muli_k = rewriter.create(loc, k, i); - //get 2*pi * k * i / N - Value muli_k = rewriter.create(loc, k , i); - - Value const2pi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(6.28318530718)); - Value mul2piKI = rewriter.create(loc, const2pi , muli_k); + Value const2pi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(6.28318530718)); + Value mul2piKI = rewriter.create(loc, const2pi, muli_k); // getOperand().getType() - // auto inputTensorType = llvm::cast(op->getOperand(0).getType()); - float LengthOfInput = (float) ub; - Value N = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(LengthOfInput)); + // auto inputTensorType = + // llvm::cast(op->getOperand(0).getType()); + float LengthOfInput = (float)ub; + Value N = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(LengthOfInput)); // Value N = inputTensorType.getShape()[0]; - Value divIndxByN = rewriter.create(loc, mul2piKI, N ) ; - + Value divIndxByN = rewriter.create(loc, mul2piKI, N); + // Img part = -1 * Sum(x[i] * sin(div) ) Value GetSin = rewriter.create(loc, divIndxByN); - Value xMulSin = rewriter.create(loc, inputX , GetSin); - Value imgSum = rewriter.create(loc, loadYImg ,xMulSin) ; + Value xMulSin = rewriter.create(loc, inputX, GetSin); + Value imgSum = rewriter.create(loc, loadYImg, xMulSin); - // Value constMinus1 = rewriter.create(loc, rewriter.getF64Type(), + // Value constMinus1 = rewriter.create(loc, + // rewriter.getF64Type(), // rewriter.getF64FloatAttr(-1)); - // Value NegImgSum = rewriter.create(loc, constMinus1 , imgSum); - rewriter.create(loc, imgSum, alloc_img, ValueRange{ivY}); + // Value NegImgSum = rewriter.create(loc, constMinus1 , + // imgSum); + rewriter.create(loc, imgSum, alloc_img, ValueRange{ivY}); // x[n-1] rewriter.setInsertionPointAfter(forOpX); // Calculate y[k] = 1/N * y[k] - + rewriter.setInsertionPointAfter(forOpY); - //debug - // forOpX->dump(); - // forOpY->dump(); - // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - - - // affine.for %y = 0 to 4 { - // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> - // // affine.store %0, %alloc_real[%arg0] : memref<4xf64> - // affine.for %x = 0 to 4 { - // // CAcluations - // %1 = affine.load %alloc_3[%x] : memref<4xf64> - // %2 = affine.load %alloc_real[%y] : memref<4xf64> - // %3 = affine.load %alloc_img[%y] : memref<4xf64> - // // index cast for multiply - // %4 = arith.index_castui %y : index to i32 - // %k = arith.uitofp %4 : i32 to f64 - // %6 = arith.index_castui %x : index to i32 - // %i = arith.uitofp %6 : i32 to f64 - // // %8 = arith.index_castui %arg3 : index to i32 - // // %9 = arith.uitofp %8 : i32 to f64 - // // %10 = arith.index_castui %arg4 : index to i32 - // // %11 = arith.uitofp %10 : i32 to f64 - - // %mul_1 = arith.mulf %i, %k : f64 - // %mul = arith.mulf %mul_1, %cst_2pi : f64 - // // ixk / N - // %div = arith.divf %mul, %N : f64 - // // cos of the above - // %res_cos = math.cos %div : f64 - // // %16 = arith.addf %14, %15 : f64 - // // %res_sin = arith.mulf %16, %cst_0 : f64 - - // %res_sin = math.sin %div : f64 - // %real_prod = arith.mulf %1, %res_cos : f64 - // %img_prod_1 = arith.mulf %1, %res_sin : f64 - // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 - - // %real = arith.addf %2, %real_prod : f64 - // %img = arith.addf %3, %img_prod : f64 - // affine.store %real, %alloc_real[%y] : memref<4xf64> - // // dsp.print %alloc_real : memref<4xf64> - // affine.store %img, %alloc_img[%y] : memref<4xf64> - - // } - // } + // debug + // forOpX->dump(); + // forOpY->dump(); + // affine.for %y = 0 to 4 { + // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + + // affine.for %y = 0 to 4 { + // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> + // // affine.store %0, %alloc_real[%arg0] : memref<4xf64> + // affine.for %x = 0 to 4 { + // // CAcluations + // %1 = affine.load %alloc_3[%x] : memref<4xf64> + // %2 = affine.load %alloc_real[%y] : memref<4xf64> + // %3 = affine.load %alloc_img[%y] : memref<4xf64> + // // index cast for multiply + // %4 = arith.index_castui %y : index to i32 + // %k = arith.uitofp %4 : i32 to f64 + // %6 = arith.index_castui %x : index to i32 + // %i = arith.uitofp %6 : i32 to f64 + // // %8 = arith.index_castui %arg3 : index to i32 + // // %9 = arith.uitofp %8 : i32 to f64 + // // %10 = arith.index_castui %arg4 : index to i32 + // // %11 = arith.uitofp %10 : i32 to f64 + + // %mul_1 = arith.mulf %i, %k : f64 + // %mul = arith.mulf %mul_1, %cst_2pi : f64 + // // ixk / N + // %div = arith.divf %mul, %N : f64 + // // cos of the above + // %res_cos = math.cos %div : f64 + // // %16 = arith.addf %14, %15 : f64 + // // %res_sin = arith.mulf %16, %cst_0 : f64 + + // %res_sin = math.sin %div : f64 + // %real_prod = arith.mulf %1, %res_cos : f64 + // %img_prod_1 = arith.mulf %1, %res_sin : f64 + // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 + + // %real = arith.addf %2, %real_prod : f64 + // %img = arith.addf %3, %img_prod : f64 + // affine.store %real, %alloc_real[%y] : memref<4xf64> + // // dsp.print %alloc_real : memref<4xf64> + // affine.store %img, %alloc_img[%y] : memref<4xf64> + + // } + // } // rewriter.replaceOp(op, alloc_real); rewriter.replaceOp(op, alloc_img); - + return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: FFT1DReal operations //===----------------------------------------------------------------------===// - struct FFT1DRealOpLowering : public ConversionPattern { FFT1DRealOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::FFT1DRealOp::getOperationName(), 1, ctx) {} @@ -4268,160 +4524,170 @@ struct FFT1DRealOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y[k] = y_real[k] + j *y_img[k] - // y_real = sumOver_n(x[n]*cos[2*pi * k *n/N ] - // y_img = sumOver_n(x[n]*sin[2*pi * k *n/N ] * -1 - //init output mem for y_real & y_img as 0 - //iterate for output from k=0 to last - //iterate for all x from n=0 to last - //perform the calculations : ie x[n] * cos[2*pi * k *n/N ] and sum and store them at y[k] - // - // replace this upsampling op with the output_mem_allocation op + + // Pseudo-code: + // y[k] = y_real[k] + j *y_img[k] + // y_real = sumOver_n(x[n]*cos[2*pi * k *n/N ] + // y_img = sumOver_n(x[n]*sin[2*pi * k *n/N ] * -1 + // init output mem for y_real & y_img as 0 + // iterate for output from k=0 to last + // iterate for all x from n=0 to last + // perform the calculations : ie x[n] * cos[2*pi * k *n/N ] and sum and + // store them at y[k] + // + // replace this upsampling op with the output_mem_allocation op // DEBUG_PRINT_NO_ARGS() ; - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - //iterate to result1 --not needed for now but for future reference - // auto tensorType1 = llvm::cast(*std::next(op->result_type_begin(), 1)); + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + // iterate to result1 --not needed for now but for future reference + // auto tensorType1 = + // llvm::cast(*std::next(op->result_type_begin(), 1)); + + // DEBUG_PRINT_NO_ARGS() ; + // tensorType.getShape()[0] + // llvm::errs() << "tensorType1.getShape()[0] " << tensorType1.getShape()[0] + // << " func= " << __func__ << "\n"; - // DEBUG_PRINT_NO_ARGS() ; - //tensorType.getShape()[0] - // llvm::errs() << "tensorType1.getShape()[0] " << tensorType1.getShape()[0] << " func= " << __func__ << "\n"; - - //allocation & deallocation for the result of this operation + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); // auto memRefType2 = convertTensorToMemRef(tensorType1); auto alloc_real = insertAllocAndDealloc(memRefType, loc, rewriter); - - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); - // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); + // affine.for %y = 0 to 4 { + // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - //For loop -- iterate from 1 to last - int64_t lb = 0 ; + // For loop -- iterate from 1 to last + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); rewriter.create(loc, constant0, alloc_real, ValueRange{iv}); rewriter.setInsertionPointAfter(forOp1); - //loop for Y - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + // loop for Y + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //loop for X - affine::AffineForOp forOpX = rewriter.create(loc, lb, ub, step); + // loop for X + affine::AffineForOp forOpX = + rewriter.create(loc, lb, ub, step); auto ivX = forOpX.getInductionVar(); rewriter.setInsertionPointToStart(forOpX.getBody()); - //load from X, & y1 & y2 + // load from X, & y1 & y2 FFT1DRealOpAdaptor fft1DrealAdaptor(operands); - Value inputX = rewriter.create(loc, fft1DrealAdaptor.getInput(), ValueRange{ivX}); - Value loadYReal = rewriter.create(loc, alloc_real, ValueRange{ivY}); - - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value k = rewriter.create(loc, rewriter.getF64Type(), IndxY); - - Value IndxX = rewriter.create(loc, rewriter.getIntegerType(32), ivX); - Value i = rewriter.create(loc, rewriter.getF64Type(), IndxX); - - //get 2*pi * k * i / N - Value muli_k = rewriter.create(loc, k , i); - - Value const2pi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(6.28318530718)); - Value mul2piKI = rewriter.create(loc, const2pi , muli_k); + Value inputX = rewriter.create( + loc, fft1DrealAdaptor.getInput(), ValueRange{ivX}); + Value loadYReal = + rewriter.create(loc, alloc_real, ValueRange{ivY}); + + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value k = + rewriter.create(loc, rewriter.getF64Type(), IndxY); + + Value IndxX = rewriter.create( + loc, rewriter.getIntegerType(32), ivX); + Value i = + rewriter.create(loc, rewriter.getF64Type(), IndxX); + + // get 2*pi * k * i / N + Value muli_k = rewriter.create(loc, k, i); + + Value const2pi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(6.28318530718)); + Value mul2piKI = rewriter.create(loc, const2pi, muli_k); // getOperand().getType() - // auto inputTensorType = llvm::cast(op->getOperand(0).getType()); - float LengthOfInput = (float) ub; - Value N = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(LengthOfInput)); + // auto inputTensorType = + // llvm::cast(op->getOperand(0).getType()); + float LengthOfInput = (float)ub; + Value N = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(LengthOfInput)); // Value N = inputTensorType.getShape()[0]; - Value divIndxByN = rewriter.create(loc, mul2piKI, N ) ; + Value divIndxByN = rewriter.create(loc, mul2piKI, N); // Real part = Sum(x[i] * cos(div) ) Value GetCos = rewriter.create(loc, divIndxByN); - Value xMulCos = rewriter.create(loc, inputX , GetCos); - Value realSum = rewriter.create(loc, loadYReal ,xMulCos) ; - rewriter.create(loc, realSum, alloc_real, ValueRange{ivY}); - + Value xMulCos = rewriter.create(loc, inputX, GetCos); + Value realSum = rewriter.create(loc, loadYReal, xMulCos); + rewriter.create(loc, realSum, alloc_real, ValueRange{ivY}); // DEBUG_PRINT_NO_ARGS() ; - + rewriter.setInsertionPointAfter(forOpX); // forOpX->dump(); // rewriter.create(loc, ValueRange{alloc_real, alloc_img}); rewriter.setInsertionPointAfter(forOpY); - //debug - // forOpX->dump(); - // forOpY->dump(); - // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - - - // affine.for %y = 0 to 4 { - // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> - // // affine.store %0, %alloc_real[%arg0] : memref<4xf64> - // affine.for %x = 0 to 4 { - // // CAcluations - // %1 = affine.load %alloc_3[%x] : memref<4xf64> - // %2 = affine.load %alloc_real[%y] : memref<4xf64> - // %3 = affine.load %alloc_img[%y] : memref<4xf64> - // // index cast for multiply - // %4 = arith.index_castui %y : index to i32 - // %k = arith.uitofp %4 : i32 to f64 - // %6 = arith.index_castui %x : index to i32 - // %i = arith.uitofp %6 : i32 to f64 - // // %8 = arith.index_castui %arg3 : index to i32 - // // %9 = arith.uitofp %8 : i32 to f64 - // // %10 = arith.index_castui %arg4 : index to i32 - // // %11 = arith.uitofp %10 : i32 to f64 - - // %mul_1 = arith.mulf %i, %k : f64 - // %mul = arith.mulf %mul_1, %cst_2pi : f64 - // // ixk / N - // %div = arith.divf %mul, %N : f64 - // // cos of the above - // %res_cos = math.cos %div : f64 - // // %16 = arith.addf %14, %15 : f64 - // // %res_sin = arith.mulf %16, %cst_0 : f64 - - // %res_sin = math.sin %div : f64 - // %real_prod = arith.mulf %1, %res_cos : f64 - // %img_prod_1 = arith.mulf %1, %res_sin : f64 - // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 - - // %real = arith.addf %2, %real_prod : f64 - // %img = arith.addf %3, %img_prod : f64 - // affine.store %real, %alloc_real[%y] : memref<4xf64> - // // dsp.print %alloc_real : memref<4xf64> - // affine.store %img, %alloc_img[%y] : memref<4xf64> - - // } - // } + // debug + // forOpX->dump(); + // forOpY->dump(); + // affine.for %y = 0 to 4 { + // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + + // affine.for %y = 0 to 4 { + // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> + // // affine.store %0, %alloc_real[%arg0] : memref<4xf64> + // affine.for %x = 0 to 4 { + // // CAcluations + // %1 = affine.load %alloc_3[%x] : memref<4xf64> + // %2 = affine.load %alloc_real[%y] : memref<4xf64> + // %3 = affine.load %alloc_img[%y] : memref<4xf64> + // // index cast for multiply + // %4 = arith.index_castui %y : index to i32 + // %k = arith.uitofp %4 : i32 to f64 + // %6 = arith.index_castui %x : index to i32 + // %i = arith.uitofp %6 : i32 to f64 + // // %8 = arith.index_castui %arg3 : index to i32 + // // %9 = arith.uitofp %8 : i32 to f64 + // // %10 = arith.index_castui %arg4 : index to i32 + // // %11 = arith.uitofp %10 : i32 to f64 + + // %mul_1 = arith.mulf %i, %k : f64 + // %mul = arith.mulf %mul_1, %cst_2pi : f64 + // // ixk / N + // %div = arith.divf %mul, %N : f64 + // // cos of the above + // %res_cos = math.cos %div : f64 + // // %16 = arith.addf %14, %15 : f64 + // // %res_sin = arith.mulf %16, %cst_0 : f64 + + // %res_sin = math.sin %div : f64 + // %real_prod = arith.mulf %1, %res_cos : f64 + // %img_prod_1 = arith.mulf %1, %res_sin : f64 + // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 + + // %real = arith.addf %2, %real_prod : f64 + // %img = arith.addf %3, %img_prod : f64 + // affine.store %real, %alloc_real[%y] : memref<4xf64> + // // dsp.print %alloc_real : memref<4xf64> + // affine.store %img, %alloc_img[%y] : memref<4xf64> + + // } + // } // rewriter.replaceOp(op, alloc_real); rewriter.replaceOp(op, alloc_real); - + return success(); } }; @@ -4438,64 +4704,64 @@ struct SquareOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - //output = 0 - //iterate for len = 0 to inputLen - // elem = a[i] - // output[i] = elem * elem - // store output - - //DEBUG_PRINT_NO_ARGS() ; - - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + + // Pseudo-code: + // output = 0 + // iterate for len = 0 to inputLen + // elem = a[i] + // output[i] = elem * elem + // store output + + // DEBUG_PRINT_NO_ARGS() ; + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); SmallVector steps(tensorType.getRank(), /*Value=*/1); - - //For loop + // For loop SquareOpAdaptor squareOpAdaptor(operands); // DEBUG_PRINT_NO_ARGS() ; - - int64_t lb = 0 ; + + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - //for loop - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + // for loop + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); - + // DEBUG_PRINT_NO_ARGS() ; - Value elemIn = rewriter.create(loc, squareOpAdaptor.getInput(), iv); - Value square = rewriter.create(loc, elemIn , elemIn); - - //store the result + Value elemIn = + rewriter.create(loc, squareOpAdaptor.getInput(), iv); + Value square = rewriter.create(loc, elemIn, elemIn); + + // store the result rewriter.create(loc, square, alloc, iv); rewriter.setInsertionPointAfter(forOp1); - //debug - // forOp1->dump(); - // affine.for %arg0 = 0 to 5 { - // %0 = affine.load %alloc_6[%arg0] : memref<5xf64> - // %1 = arith.mulf %0, %0 : f64 - // affine.store %1, %alloc_5[%arg0] : memref<5xf64> - // } + // debug + // forOp1->dump(); + // affine.for %arg0 = 0 to 5 { + // %0 = affine.load %alloc_6[%arg0] : memref<5xf64> + // %1 = arith.mulf %0, %0 : f64 + // affine.store %1, %alloc_5[%arg0] : memref<5xf64> + // } rewriter.replaceOp(op, alloc); - + return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: SumOp operations //===----------------------------------------------------------------------===// @@ -4508,128 +4774,134 @@ struct SumOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - //output = 0 - //iterate for len = 0 to inputLen - // output = load output - // elem = a[i] - // output = output + elem - // store output + + // Pseudo-code: + // output = 0 + // iterate for len = 0 to inputLen + // output = load output + // elem = a[i] + // output = output + elem + // store output // DEBUG_PRINT_NO_ARGS() ; - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); SmallVector steps(tensorType.getRank(), /*Value=*/1); - - //For loop + // For loop SumOpAdaptor sumOpAdaptor(operands); // DEBUG_PRINT_NO_ARGS() ; - auto inputType = llvm::dyn_cast(op->getOperand(0).getType()); //op->getOperand( - // auto inputType = llvm::dyn_cast(sumOpAdaptor.getInput().getType()); + auto inputType = llvm::dyn_cast( + op->getOperand(0).getType()); // op->getOperand( + // auto inputType = + // llvm::dyn_cast(sumOpAdaptor.getInput().getType()); // DEBUG_PRINT_NO_ARGS() ; - int64_t lb = 0 ; + int64_t lb = 0; int64_t ub = inputType.getShape()[0]; int64_t step = 1; - //init 0 for output + // init 0 for output Value constantIndx0 = rewriter.create(loc, 0); - // Value GetInputX0 = rewriter.create(loc, lowPassFilterAdaptor.getLhs(), /* iv */ ValueRange{constantIndx0}); - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - // Value elemIn = rewriter.create(loc, upsamplingAdaptor.getLhs(), iv); - // DEBUG_PRINT_NO_ARGS() ; - rewriter.create(loc, constant0, alloc, ValueRange{constantIndx0}); - - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + // Value GetInputX0 = rewriter.create(loc, + // lowPassFilterAdaptor.getLhs(), /* iv */ ValueRange{constantIndx0}); + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + // Value elemIn = rewriter.create(loc, + // upsamplingAdaptor.getLhs(), iv); DEBUG_PRINT_NO_ARGS() ; + rewriter.create(loc, constant0, alloc, + ValueRange{constantIndx0}); + + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); - + // DEBUG_PRINT_NO_ARGS() ; - Value elemIn = rewriter.create(loc, sumOpAdaptor.getInput(), iv); - Value loadSum = rewriter.create(loc, alloc, ValueRange{constantIndx0}); - - Value sum = rewriter.create(loc, elemIn , loadSum); - - //store the result + Value elemIn = + rewriter.create(loc, sumOpAdaptor.getInput(), iv); + Value loadSum = + rewriter.create(loc, alloc, ValueRange{constantIndx0}); + + Value sum = rewriter.create(loc, elemIn, loadSum); + + // store the result rewriter.create(loc, sum, alloc, ValueRange{constantIndx0}); rewriter.setInsertionPointAfter(forOp1); - //debug - // forOp1->dump(); - // %cont3 = arith.const 3 : f64 - // affine.for %arg0 = 0 to 8 { - // %elem1 = affine.load input[%arg0] - // #map1 = affine_map<(%arg0)[] : (%arg0 + 1) - // #map2 = affine_map<(%arg0)[] : (%arg0 + 2) - // %elem2 = affine.load input[#map1] <-- affine apply - // %elem3 = affine.load input[#map2] - - // %sum1 = arith.addf %elem1 , %elem2 - // %sum2 = arith.addf %sum1, %elem3 - // %res = arith.divf %sum2 , - // affine.store %sum2, out[%arg0] - // } + // debug + // forOp1->dump(); + // %cont3 = arith.const 3 : f64 + // affine.for %arg0 = 0 to 8 { + // %elem1 = affine.load input[%arg0] + // #map1 = affine_map<(%arg0)[] : (%arg0 + 1) + // #map2 = affine_map<(%arg0)[] : (%arg0 + 2) + // %elem2 = affine.load input[#map1] <-- affine apply + // %elem3 = affine.load input[#map2] + + // %sum1 = arith.addf %elem1 , %elem2 + // %sum2 = arith.addf %sum1, %elem3 + // %res = arith.divf %sum2 , + // affine.store %sum2, out[%arg0] + // } rewriter.replaceOp(op, alloc); - + return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: FIRFilterResponse operations //===----------------------------------------------------------------------===// -struct filterOpLowering: public ConversionPattern { - filterOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::filterOp::getOperationName(), 1 , ctx) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - //dsp.filterOp has 3 operands -- both of type tensor f64 - - //Pseudo-code: - // y[i] = sum(b[j] * x(i-j) - a[j] *x[i-j] ) j=1 to i and i=1 to len(x) - // also, y[0] = b[0] * x[0] - +struct filterOpLowering : public ConversionPattern { + filterOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::filterOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + // dsp.filterOp has 3 operands -- both of type tensor f64 + + // Pseudo-code: + // y[i] = sum(b[j] * x(i-j) - a[j] *x[i-j] ) j=1 to i and i=1 to len(x) + // also, y[0] = b[0] * x[0] + // 1) calculate y[0] // 2) iterate for indx=1 to input_len: // load y[indx] = b[0] * x[indx] - // 3) iterate for j=1 to indx : + // 3) iterate for j=1 to indx : // load b[j] , x[i-j] , a[j] , y[i-j] // y[indx] = y[indx] + b[j] * x[i-j] - a[j]*y[i-j] auto loc = op->getLoc(); - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); filterOpAdaptor filterOpAdaptor1(operands); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); // IR: - // ConstantIndx0 + // ConstantIndx0 // b0 = affine.load(b, ConstantIndx0) // x0 = affine.load(x, ConstantIndx0) // tempY0 = arith.mulf(b0,x0) - // lb = 1, ub = x.size() , ivY = forLoopY.inductionVariable() + // lb = 1, ub = x.size() , ivY = forLoopY.inductionVariable() // forLoopY // xIvY = affine.load(x,ivY ) // tempYIndx = affine.mulf(b0, xIvY) @@ -4637,7 +4909,7 @@ struct filterOpLowering: public ConversionPattern { // forloopJ , ivJ = forloopJ.inductionVariable() // //optional get min ivY and len(b) -- iterate for this - // load (b,ivJ) ; (x, map(ivY - ivJ)) , (a, ivJ) , + // load (b,ivJ) ; (x, map(ivY - ivJ)) , (a, ivJ) , // (y, map(ivY - ivJ) ), (y , ivJ) // tempBxX = arith.mulf(b , x) @@ -4646,83 +4918,95 @@ struct filterOpLowering: public ConversionPattern { // sumY_A = arith.addf( Y , tempB_A ) // affine.store(sumY_A , y , ivY) - // ConstantIndx0 + // ConstantIndx0 // b0 = affine.load(b, ConstantIndx0) // x0 = affine.load(x, ConstantIndx0) // tempY0 = arith.mulf(b0,x0) Value constantIndx0 = rewriter.create(loc, 0); - Value b0 = rewriter.create(loc, filterOpAdaptor1.getB() ,ValueRange{constantIndx0} ); - Value x0 = rewriter.create(loc, filterOpAdaptor1.getX() ,ValueRange{constantIndx0} ); + Value b0 = rewriter.create( + loc, filterOpAdaptor1.getB(), ValueRange{constantIndx0}); + Value x0 = rewriter.create( + loc, filterOpAdaptor1.getX(), ValueRange{constantIndx0}); Value tempY0 = rewriter.create(loc, b0, x0); - //store at Y0 - rewriter.create(loc, tempY0 , alloc,ValueRange{constantIndx0} ); + // store at Y0 + rewriter.create(loc, tempY0, alloc, + ValueRange{constantIndx0}); - //For loop -- iterate from 1 to last - // lb = 1, ub = x.size() , ivY = forLoopY.inductionVariable() - // forLoopY - // xIvY = affine.load(x,ivY ) - // tempYIndx = affine.mulf(b0, xIvY) - // affine.store(tempYIndx, y, ivY) + // For loop -- iterate from 1 to last + // lb = 1, ub = x.size() , ivY = forLoopY.inductionVariable() + // forLoopY + // xIvY = affine.load(x,ivY ) + // tempYIndx = affine.mulf(b0, xIvY) + // affine.store(tempYIndx, y, ivY) - int64_t lb = 1 ; + int64_t lb = 1; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; // DEBUG_PRINT_NO_ARGS() ; - //loop for Y - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + // loop for Y + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - Value xIvY = rewriter.create(loc, filterOpAdaptor1.getX() , ivY); + Value xIvY = rewriter.create( + loc, filterOpAdaptor1.getX(), ivY); Value b0mulxIvY = rewriter.create(loc, b0, xIvY); - rewriter.create(loc, b0mulxIvY , alloc,ivY ); + rewriter.create(loc, b0mulxIvY, alloc, ivY); - //loop for X-- 1 to upperIndx ie, ivY - // forloopJ , ivJ = forloopJ.inductionVariable() - // //optional get min ivY and len(b) -- iterate for this - // load (b,ivJ) ; (x, map(ivY - ivJ)) , (a, ivJ) , - // (y, map(ivY - ivJ) ), (y , ivJ) + // loop for X-- 1 to upperIndx ie, ivY + // forloopJ , ivJ = forloopJ.inductionVariable() + // //optional get min ivY and len(b) -- iterate for this + // load (b,ivJ) ; (x, map(ivY - ivJ)) , (a, ivJ) , + // (y, map(ivY - ivJ) ), (y , ivJ) - // tempBxX = arith.mulf(b , x) - // tempAxY = arith.mulf(a , Y_i-j) - // tempB_A = arith.subf( tempBxX - tempAxY) - // sumY_A = arith.addf( Y , tempB_A ) - // affine.store(sumY_A , y , ivY) + // tempBxX = arith.mulf(b , x) + // tempAxY = arith.mulf(a , Y_i-j) + // tempB_A = arith.subf( tempBxX - tempAxY) + // sumY_A = arith.addf( Y , tempB_A ) + // affine.store(sumY_A , y , ivY) - //look for here - // DEBUG_PRINT_NO_ARGS() ; - //Future -- try to loop - // Value forlb = rewriter.create(loc, 1); + // look for here + // DEBUG_PRINT_NO_ARGS() ; + // Future -- try to loop + // Value forlb = rewriter.create(loc, 1); AffineExpr expr0; bindDims(rewriter.getContext(), expr0); // AffineMap lbMap = AffineMap::get(1, 0, expr0); - // affine::AffineForOp forOpJ = rewriter.create(loc, lbMap, ValueRange{forlb} ,lbMap , ValueRange{ivY}, step); - affine::AffineForOp forOpJ = rewriter.create(loc, lb, ub, step); + // affine::AffineForOp forOpJ = rewriter.create(loc, lbMap, + // ValueRange{forlb} ,lbMap , ValueRange{ivY}, step); + affine::AffineForOp forOpJ = + rewriter.create(loc, lb, ub, step); auto ivJ = forOpJ.getInductionVar(); rewriter.setInsertionPointToStart(forOpJ.getBody()); - //load from X, & Y - // DCTOpAdaptor dctAdaptor(operands); - //For affine expression: #map1 = affine_map<(%ivY , ivJ)[] : (%ivY - ivJ) + // load from X, & Y + // DCTOpAdaptor dctAdaptor(operands); + // For affine expression: #map1 = affine_map<(%ivY , ivJ)[] : (%ivY - ivJ) AffineExpr d0, d1, s0; bindDims(rewriter.getContext(), d0, d1); - // AffineExpr ExprForIndxYminusX = rewriter.getAffineDimExpr(0) - rewriter.getAffineDimExpr(1); //d0 - d1; - AffineExpr ExprForIndxYminusX = d0 - d1; + // AffineExpr ExprForIndxYminusX = rewriter.getAffineDimExpr(0) - + // rewriter.getAffineDimExpr(1); //d0 - d1; + AffineExpr ExprForIndxYminusX = d0 - d1; AffineMap addMapForYminusX = AffineMap::get(2, 0, ExprForIndxYminusX); - // load (b,ivJ) ; (x, map(ivY - ivJ)) , (a, ivJ) , + // load (b,ivJ) ; (x, map(ivY - ivJ)) , (a, ivJ) , // (y, map(ivY - ivJ) ), (y , ivJ) - Value inputX = rewriter.create(loc, filterOpAdaptor1.getX(),addMapForYminusX, ValueRange{ivY,ivJ}); - Value inputB = rewriter.create(loc, filterOpAdaptor1.getB(), ValueRange{ivJ}); - Value inputA = rewriter.create(loc, filterOpAdaptor1.getA(), ValueRange{ivJ}); - Value inputPrevY = rewriter.create(loc, alloc,addMapForYminusX, ValueRange{ivY,ivJ}); + Value inputX = rewriter.create( + loc, filterOpAdaptor1.getX(), addMapForYminusX, ValueRange{ivY, ivJ}); + Value inputB = rewriter.create(loc, filterOpAdaptor1.getB(), + ValueRange{ivJ}); + Value inputA = rewriter.create(loc, filterOpAdaptor1.getA(), + ValueRange{ivJ}); + Value inputPrevY = rewriter.create( + loc, alloc, addMapForYminusX, ValueRange{ivY, ivJ}); Value outY = rewriter.create(loc, alloc, ValueRange{ivY}); // tempBxX = arith.mulf(b , x) @@ -4735,77 +5019,71 @@ struct filterOpLowering: public ConversionPattern { Value tempAxY = rewriter.create(loc, inputA, inputPrevY); Value tempBminusA = rewriter.create(loc, tempBxX, tempAxY); Value sumY_A = rewriter.create(loc, outY, tempBminusA); - rewriter.create(loc, sumY_A , alloc,ivY ); + rewriter.create(loc, sumY_A, alloc, ivY); - rewriter.setInsertionPointAfter(forOpJ); rewriter.setInsertionPointAfter(forOpY); // forOpJ->dump(); - - //debug - // forOpJ->dump(); - // forOpY->dump(); - // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - - - // affine.for %y = 0 to 4 { - // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> - // // affine.store %0, %alloc[%arg0] : memref<4xf64> - // affine.for %x = 0 to 4 { - // // CAcluations - // %1 = affine.load %alloc_3[%x] : memref<4xf64> - // %2 = affine.load %alloc[%y] : memref<4xf64> - // %3 = affine.load %alloc_img[%y] : memref<4xf64> - // // index cast for multiply - // %4 = arith.index_castui %y : index to i32 - // %k = arith.uitofp %4 : i32 to f64 - // %6 = arith.index_castui %x : index to i32 - // %i = arith.uitofp %6 : i32 to f64 - // // %8 = arith.index_castui %arg3 : index to i32 - // // %9 = arith.uitofp %8 : i32 to f64 - // // %10 = arith.index_castui %arg4 : index to i32 - // // %11 = arith.uitofp %10 : i32 to f64 - - // %mul_1 = arith.mulf %i, %k : f64 - // %mul = arith.mulf %mul_1, %cst_2pi : f64 - // // ixk / N - // %div = arith.divf %mul, %N : f64 - // // cos of the above - // %res_cos = math.cos %div : f64 - // // %16 = arith.addf %14, %15 : f64 - // // %res_sin = arith.mulf %16, %cst_0 : f64 - - // %res_sin = math.sin %div : f64 - // %real_prod = arith.mulf %1, %res_cos : f64 - // %img_prod_1 = arith.mulf %1, %res_sin : f64 - // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 - - // %real = arith.addf %2, %real_prod : f64 - // %img = arith.addf %3, %img_prod : f64 - // affine.store %real, %alloc[%y] : memref<4xf64> - // // dsp.print %alloc : memref<4xf64> - // affine.store %img, %alloc_img[%y] : memref<4xf64> - - // } - // } + + // debug + // forOpJ->dump(); + // forOpY->dump(); + // affine.for %y = 0 to 4 { + // affine.store %cst_3, %alloc[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + + // affine.for %y = 0 to 4 { + // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> + // // affine.store %0, %alloc[%arg0] : memref<4xf64> + // affine.for %x = 0 to 4 { + // // CAcluations + // %1 = affine.load %alloc_3[%x] : memref<4xf64> + // %2 = affine.load %alloc[%y] : memref<4xf64> + // %3 = affine.load %alloc_img[%y] : memref<4xf64> + // // index cast for multiply + // %4 = arith.index_castui %y : index to i32 + // %k = arith.uitofp %4 : i32 to f64 + // %6 = arith.index_castui %x : index to i32 + // %i = arith.uitofp %6 : i32 to f64 + // // %8 = arith.index_castui %arg3 : index to i32 + // // %9 = arith.uitofp %8 : i32 to f64 + // // %10 = arith.index_castui %arg4 : index to i32 + // // %11 = arith.uitofp %10 : i32 to f64 + + // %mul_1 = arith.mulf %i, %k : f64 + // %mul = arith.mulf %mul_1, %cst_2pi : f64 + // // ixk / N + // %div = arith.divf %mul, %N : f64 + // // cos of the above + // %res_cos = math.cos %div : f64 + // // %16 = arith.addf %14, %15 : f64 + // // %res_sin = arith.mulf %16, %cst_0 : f64 + + // %res_sin = math.sin %div : f64 + // %real_prod = arith.mulf %1, %res_cos : f64 + // %img_prod_1 = arith.mulf %1, %res_sin : f64 + // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 + + // %real = arith.addf %2, %real_prod : f64 + // %img = arith.addf %3, %img_prod : f64 + // affine.store %real, %alloc[%y] : memref<4xf64> + // // dsp.print %alloc : memref<4xf64> + // affine.store %img, %alloc_img[%y] : memref<4xf64> + + // } + // } rewriter.replaceOp(op, alloc); // rewriter.replaceOp(op, ValueRange{alloc,alloc_img}); - - return success(); - } - + return success(); + } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: DCT operations //===----------------------------------------------------------------------===// - struct DCTOpLowering : public ConversionPattern { DCTOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::DCTOp::getOperationName(), 1, ctx) {} @@ -4814,187 +5092,201 @@ struct DCTOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y[k] = sqrt(2/N) * SumOverAllN( x[n] cos(pi * k * (n +0.5)/N)) , 0<=n<=N-1 : - // for y[0] , the answer will be multiplied by 1/sqrt(2) - - //init output mem for y as 0 - //iterate for output from k=0 to last - //iterate for all x from n=0 to last - //perform the calculations : ie x[n] cos(pi * k * (n +0.5)/N) and sum and store them at y[k] - // - // replace this upsampling op with the output_mem_allocation op + + // Pseudo-code: + // y[k] = sqrt(2/N) * SumOverAllN( x[n] cos(pi * k * (n +0.5)/N)) , + // 0<=n<=N-1 : + // for y[0] , the answer will be multiplied by 1/sqrt(2) + + // init output mem for y as 0 + // iterate for output from k=0 to last + // iterate for all x from n=0 to last + // perform the calculations : ie x[n] cos(pi * k * (n +0.5)/N) and sum and + // store them at y[k] + // + // replace this upsampling op with the output_mem_allocation op // DEBUG_PRINT_NO_ARGS() ; - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); DCTOpAdaptor dctAdaptor(operands); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); - //constant values: + // constant values: const float sqrt2 = 1.41421356237; const float pi = 3.14159265358; // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc[%y] : memref<4xf64> - // } - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); - + // affine.store %cst_3, %alloc[%y] : memref<4xf64> + // } + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - //For loop -- iterate from 0 to last - int64_t lb = 0 ; + // For loop -- iterate from 0 to last + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); rewriter.create(loc, constant0, alloc, ValueRange{iv}); rewriter.setInsertionPointAfter(forOp1); // DEBUG_PRINT_NO_ARGS() ; - //loop for Y - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + // loop for Y + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //loop for X - affine::AffineForOp forOpX = rewriter.create(loc, lb, ub, step); + // loop for X + affine::AffineForOp forOpX = + rewriter.create(loc, lb, ub, step); auto ivX = forOpX.getInductionVar(); rewriter.setInsertionPointToStart(forOpX.getBody()); - //load from X, & Y - // DCTOpAdaptor dctAdaptor(operands); - Value inputX = rewriter.create(loc, dctAdaptor.getInput(), ValueRange{ivX}); - Value loadYReal = rewriter.create(loc, alloc, ValueRange{ivY}); + // load from X, & Y + // DCTOpAdaptor dctAdaptor(operands); + Value inputX = rewriter.create(loc, dctAdaptor.getInput(), + ValueRange{ivX}); + Value loadYReal = + rewriter.create(loc, alloc, ValueRange{ivY}); - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value k = rewriter.create(loc, rewriter.getF64Type(), IndxY); + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value k = + rewriter.create(loc, rewriter.getF64Type(), IndxY); - Value IndxX = rewriter.create(loc, rewriter.getIntegerType(32), ivX); - Value i = rewriter.create(loc, rewriter.getF64Type(), IndxX); + Value IndxX = rewriter.create( + loc, rewriter.getIntegerType(32), ivX); + Value i = + rewriter.create(loc, rewriter.getF64Type(), IndxX); - //get pi * k * (i + 0.5) / N - Value constant0_5 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0.5)); + // get pi * k * (i + 0.5) / N + Value constant0_5 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0.5)); Value add_i_half = rewriter.create(loc, i, constant0_5); - Value muli_k = rewriter.create(loc, k , add_i_half); - - Value constpi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(pi)); - Value mulpiKI_half = rewriter.create(loc, constpi , muli_k); + Value muli_k = rewriter.create(loc, k, add_i_half); + + Value constpi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(pi)); + Value mulpiKI_half = rewriter.create(loc, constpi, muli_k); // Get N // DEBUG_PRINT_NO_ARGS() ; - float LengthOfInput = (float) ub; - Value N = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(LengthOfInput)); + float LengthOfInput = (float)ub; + Value N = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(LengthOfInput)); - Value divIndxByN = rewriter.create(loc, mulpiKI_half, N ) ; + Value divIndxByN = rewriter.create(loc, mulpiKI_half, N); // Get cos ( pi * k * (n +0.5)/N)) // DEBUG_PRINT_NO_ARGS() ; Value GetCos = rewriter.create(loc, divIndxByN); - Value xMulCos = rewriter.create(loc, inputX , GetCos); - Value realSum = rewriter.create(loc, loadYReal ,xMulCos) ; - rewriter.create(loc, realSum, alloc, ValueRange{ivY}); - + Value xMulCos = rewriter.create(loc, inputX, GetCos); + Value realSum = rewriter.create(loc, loadYReal, xMulCos); + rewriter.create(loc, realSum, alloc, ValueRange{ivY}); + rewriter.setInsertionPointAfter(forOpX); - //multiply Y(k) with sqrt(2) / sqrt(N) - // DEBUG_PRINT_NO_ARGS() ; - Value loadYReal1 = rewriter.create(loc, alloc, ValueRange{ivY}); - Value constSqrt2 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(sqrt2)); + // multiply Y(k) with sqrt(2) / sqrt(N) + // DEBUG_PRINT_NO_ARGS() ; + Value loadYReal1 = + rewriter.create(loc, alloc, ValueRange{ivY}); + Value constSqrt2 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(sqrt2)); // Type floatType = rewriter.getF64Type(); - Value N2 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(LengthOfInput)); + Value N2 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(LengthOfInput)); // Define fast math flags // auto fastMathFlags = arith::FastMathFlagsAttr::get( // rewriter.getContext(), arith::FastMathFlags::none); - // arith::FastMathFlags::ApproximateSqrt | - // arith::FastMathFlags::AllowReciprocal); - Value sqrtN = rewriter.create(loc, N2 ); - // Value sqrtN = rewriter.create(loc, TypeRange{ floatType } , N2 , fastMathFlags ); - - Value mulSqrt2ByN = rewriter.create(loc, constSqrt2 , sqrtN); - Value mulSqrt2ByNByY = rewriter.create(loc, mulSqrt2ByN , loadYReal1); + // arith::FastMathFlags::ApproximateSqrt | + // arith::FastMathFlags::AllowReciprocal); + Value sqrtN = rewriter.create(loc, N2); + // Value sqrtN = rewriter.create(loc, TypeRange{ floatType } + // , N2 , fastMathFlags ); + + Value mulSqrt2ByN = rewriter.create(loc, constSqrt2, sqrtN); + Value mulSqrt2ByNByY = + rewriter.create(loc, mulSqrt2ByN, loadYReal1); // DEBUG_PRINT_NO_ARGS() ; - rewriter.create(loc, mulSqrt2ByNByY, alloc, ValueRange{ivY}); + rewriter.create(loc, mulSqrt2ByNByY, alloc, ValueRange{ivY}); rewriter.setInsertionPointAfter(forOpY); - //get Y0 multiplied by sqrt(2) + // get Y0 multiplied by sqrt(2) Value constantIndx0 = rewriter.create(loc, 0); - Value GetY0 = rewriter.create(loc, alloc, /* iv */ ValueRange{constantIndx0}); - Value valSqrt2 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(sqrt2)); + Value GetY0 = rewriter.create( + loc, alloc, /* iv */ ValueRange{constantIndx0}); + Value valSqrt2 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(sqrt2)); Value Y0MulSqrt2 = rewriter.create(loc, GetY0, valSqrt2); - rewriter.create(loc, Y0MulSqrt2, alloc, ValueRange{constantIndx0}); - - //debug - // forOpX->dump(); - // forOpY->dump(); - // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - - - // affine.for %y = 0 to 4 { - // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> - // // affine.store %0, %alloc[%arg0] : memref<4xf64> - // affine.for %x = 0 to 4 { - // // CAcluations - // %1 = affine.load %alloc_3[%x] : memref<4xf64> - // %2 = affine.load %alloc[%y] : memref<4xf64> - // %3 = affine.load %alloc_img[%y] : memref<4xf64> - // // index cast for multiply - // %4 = arith.index_castui %y : index to i32 - // %k = arith.uitofp %4 : i32 to f64 - // %6 = arith.index_castui %x : index to i32 - // %i = arith.uitofp %6 : i32 to f64 - // // %8 = arith.index_castui %arg3 : index to i32 - // // %9 = arith.uitofp %8 : i32 to f64 - // // %10 = arith.index_castui %arg4 : index to i32 - // // %11 = arith.uitofp %10 : i32 to f64 - - // %mul_1 = arith.mulf %i, %k : f64 - // %mul = arith.mulf %mul_1, %cst_2pi : f64 - // // ixk / N - // %div = arith.divf %mul, %N : f64 - // // cos of the above - // %res_cos = math.cos %div : f64 - // // %16 = arith.addf %14, %15 : f64 - // // %res_sin = arith.mulf %16, %cst_0 : f64 - - // %res_sin = math.sin %div : f64 - // %real_prod = arith.mulf %1, %res_cos : f64 - // %img_prod_1 = arith.mulf %1, %res_sin : f64 - // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 - - // %real = arith.addf %2, %real_prod : f64 - // %img = arith.addf %3, %img_prod : f64 - // affine.store %real, %alloc[%y] : memref<4xf64> - // // dsp.print %alloc : memref<4xf64> - // affine.store %img, %alloc_img[%y] : memref<4xf64> - - // } - // } + rewriter.create(loc, Y0MulSqrt2, alloc, + ValueRange{constantIndx0}); + + // debug + // forOpX->dump(); + // forOpY->dump(); + // affine.for %y = 0 to 4 { + // affine.store %cst_3, %alloc[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + + // affine.for %y = 0 to 4 { + // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> + // // affine.store %0, %alloc[%arg0] : memref<4xf64> + // affine.for %x = 0 to 4 { + // // CAcluations + // %1 = affine.load %alloc_3[%x] : memref<4xf64> + // %2 = affine.load %alloc[%y] : memref<4xf64> + // %3 = affine.load %alloc_img[%y] : memref<4xf64> + // // index cast for multiply + // %4 = arith.index_castui %y : index to i32 + // %k = arith.uitofp %4 : i32 to f64 + // %6 = arith.index_castui %x : index to i32 + // %i = arith.uitofp %6 : i32 to f64 + // // %8 = arith.index_castui %arg3 : index to i32 + // // %9 = arith.uitofp %8 : i32 to f64 + // // %10 = arith.index_castui %arg4 : index to i32 + // // %11 = arith.uitofp %10 : i32 to f64 + + // %mul_1 = arith.mulf %i, %k : f64 + // %mul = arith.mulf %mul_1, %cst_2pi : f64 + // // ixk / N + // %div = arith.divf %mul, %N : f64 + // // cos of the above + // %res_cos = math.cos %div : f64 + // // %16 = arith.addf %14, %15 : f64 + // // %res_sin = arith.mulf %16, %cst_0 : f64 + + // %res_sin = math.sin %div : f64 + // %real_prod = arith.mulf %1, %res_cos : f64 + // %img_prod_1 = arith.mulf %1, %res_sin : f64 + // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 + + // %real = arith.addf %2, %real_prod : f64 + // %img = arith.addf %3, %img_prod : f64 + // affine.store %real, %alloc[%y] : memref<4xf64> + // // dsp.print %alloc : memref<4xf64> + // affine.store %img, %alloc_img[%y] : memref<4xf64> + + // } + // } rewriter.replaceOp(op, alloc); // rewriter.replaceOp(op, ValueRange{alloc,alloc_img}); - + return success(); } }; @@ -5011,95 +5303,98 @@ struct HammingWindowOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y[k] = 0.54 - 0.46 cos(2 *pi * k/N-1) , 0<=n((*op->result_type_begin())); - // llvm::errs() << "tensorType " << tensorType.get; - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + // llvm::errs() << "tensorType " << tensorType.get; + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); - - - //For loop -- iterate from 1 to last - DEBUG_PRINT_NO_ARGS() ; - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0]; - int64_t step = 1; - DEBUG_PRINT_NO_ARGS() ; - //get constants -- 0.54 & 0.46 - Value constant0_54 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0.54)); - Value constant0_46 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0.46)); - Value const2pi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(6.28318530718)); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); + // For loop -- iterate from 1 to last + DEBUG_PRINT_NO_ARGS(); + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; - //loop for Y - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + DEBUG_PRINT_NO_ARGS(); + // get constants -- 0.54 & 0.46 + Value constant0_54 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0.54)); + Value constant0_46 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0.46)); + Value const2pi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(6.28318530718)); + + // loop for Y + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value k = rewriter.create(loc, rewriter.getF64Type(), IndxY); + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value k = + rewriter.create(loc, rewriter.getF64Type(), IndxY); - - //get 2*pi * k / (N -1) - Value mul2pi_k = rewriter.create(loc, const2pi , k); + // get 2*pi * k / (N -1) + Value mul2pi_k = rewriter.create(loc, const2pi, k); // getOperand().getType() - // auto inputTensorType = llvm::cast(op->getOperand(0).getType()); - float LengthOfInput = (float) ub ; - Value NMinus1 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(LengthOfInput - 1)); - - Value divIndxByNMinus1 = rewriter.create(loc, mul2pi_k, NMinus1 ) ; + // auto inputTensorType = + // llvm::cast(op->getOperand(0).getType()); + float LengthOfInput = (float)ub; + Value NMinus1 = rewriter.create( + loc, rewriter.getF64Type(), + rewriter.getF64FloatAttr(LengthOfInput - 1)); + + Value divIndxByNMinus1 = + rewriter.create(loc, mul2pi_k, NMinus1); // get cos(2*pi * k/(N-1) Value GetCos = rewriter.create(loc, divIndxByNMinus1); - Value MulCos0_46 = rewriter.create(loc, constant0_46 , GetCos); - Value Sub0_54_Cos = rewriter.create(loc, constant0_54 ,MulCos0_46) ; - rewriter.create(loc, Sub0_54_Cos, alloc, ValueRange{ivY}); - DEBUG_PRINT_NO_ARGS() ; + Value MulCos0_46 = + rewriter.create(loc, constant0_46, GetCos); + Value Sub0_54_Cos = + rewriter.create(loc, constant0_54, MulCos0_46); + rewriter.create(loc, Sub0_54_Cos, alloc, ValueRange{ivY}); + DEBUG_PRINT_NO_ARGS(); rewriter.setInsertionPointAfter(forOpY); - //debug - // forOpX->dump(); - // forOpY->dump(); - + // debug + // forOpX->dump(); + // forOpY->dump(); + + // %cst = arith.constant 6.2831853071800001 : f64 + // %cst_0 = arith.constant 4.600000e-01 : f64 + // %cst_1 = arith.constant 5.400000e-01 : f64 + // %cst_2 = arith.constant 4.000000e+00 : f64 + // %alloc = memref.alloc() : memref<4xf64> + // %alloc_3 = memref.alloc() : memref + // affine.store %cst_2, %alloc_3[] : memref + // affine.for %arg0 = 0 to 4 { + // %0 = arith.index_castui %arg0 : index to i32 + // %1 = arith.uitofp %0 : i32 to f64 + // %2 = arith.mulf %1, %cst : f64 + // %3 = arith.divf %2, %cst_2 : f64 + // %4 = math.cos %3 : f64 + // %5 = arith.mulf %4, %cst_0 : f64 + // %6 = arith.subf %cst_1, %5 : f64 + // affine.store %6, %alloc[%arg0] : memref<4xf64> + // } - // %cst = arith.constant 6.2831853071800001 : f64 - // %cst_0 = arith.constant 4.600000e-01 : f64 - // %cst_1 = arith.constant 5.400000e-01 : f64 - // %cst_2 = arith.constant 4.000000e+00 : f64 - // %alloc = memref.alloc() : memref<4xf64> - // %alloc_3 = memref.alloc() : memref - // affine.store %cst_2, %alloc_3[] : memref - // affine.for %arg0 = 0 to 4 { - // %0 = arith.index_castui %arg0 : index to i32 - // %1 = arith.uitofp %0 : i32 to f64 - // %2 = arith.mulf %1, %cst : f64 - // %3 = arith.divf %2, %cst_2 : f64 - // %4 = math.cos %3 : f64 - // %5 = arith.mulf %4, %cst_0 : f64 - // %6 = arith.subf %cst_1, %5 : f64 - // affine.store %6, %alloc[%arg0] : memref<4xf64> - // } - - - // } - // } + // } + // } rewriter.replaceOp(op, alloc); - //rewriter.replaceOp(op, ValueRange{alloc,alloc_img}); - + // rewriter.replaceOp(op, ValueRange{alloc,alloc_img}); + return success(); } }; @@ -5112,191 +5407,198 @@ struct IFFT1DOpLowering : public ConversionPattern { IFFT1DOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::IFFT1DOp::getOperationName(), 1, ctx) {} - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - auto loc = op->getLoc(); - - //Pseudo-code: - // y[k] = y_real[k] + j *y_img[k] - // y_real = sumOver_n(x[k]*cos[2*pi * k *n/N ] - // y_img = sumOver_n(x[k]*sin[2*pi * k *n/N ] - // here, x[k] is complex ie, x_real[k] + x_complex[k] - //so, y[k] = sumOver_n(x[k]e^(2*pi * k *n/N)) - // ==> = x_real[k]cos(2*pi * k *n/N) - x_complex[k]sin(2*pi * k *n/N) - - //init output mem for y_real - //iterate for output from k=0 to last - //iterate for all x from n=0 to last - //perform the calculations : ie x_real[k]cos(2*pi * k *n/N) - x_complex[k]sin(2*pi * k *n/N) and - //sum and store them at y[k] - // - - DEBUG_PRINT_NO_ARGS() ; - - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - //iterate to result1 --not needed for now but for future reference - // DEBUG_PRINT_NO_ARGS() ; - - //allocation & deallocation for the result of this operation + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + // Pseudo-code: + // y[k] = y_real[k] + j *y_img[k] + // y_real = sumOver_n(x[k]*cos[2*pi * k *n/N ] + // y_img = sumOver_n(x[k]*sin[2*pi * k *n/N ] + // here, x[k] is complex ie, x_real[k] + x_complex[k] + // so, y[k] = sumOver_n(x[k]e^(2*pi * k *n/N)) + // ==> = x_real[k]cos(2*pi * k *n/N) - x_complex[k]sin(2*pi * k *n/N) + + // init output mem for y_real + // iterate for output from k=0 to last + // iterate for all x from n=0 to last + // perform the calculations : ie x_real[k]cos(2*pi * k *n/N) - + // x_complex[k]sin(2*pi * k *n/N) and sum and store them at y[k] + // + + DEBUG_PRINT_NO_ARGS(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + // iterate to result1 --not needed for now but for future reference + // DEBUG_PRINT_NO_ARGS() ; + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc_real = insertAllocAndDealloc(memRefType, loc, rewriter); - - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); - - DEBUG_PRINT_NO_ARGS() ; - //For loop -- iterate from 0 to last - int64_t lb = 0 ; + // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + + DEBUG_PRINT_NO_ARGS(); + // For loop -- iterate from 0 to last + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); rewriter.create(loc, constant0, alloc_real, ValueRange{iv}); rewriter.setInsertionPointAfter(forOp1); - //loop for Y - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + // loop for Y + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //loop for X - affine::AffineForOp forOpX = rewriter.create(loc, lb, ub, step); + // loop for X + affine::AffineForOp forOpX = + rewriter.create(loc, lb, ub, step); auto ivX = forOpX.getInductionVar(); rewriter.setInsertionPointToStart(forOpX.getBody()); - //load from X, & y1 & y2 + // load from X, & y1 & y2 IFFT1DOpAdaptor ifft1DAdaptor(operands); - Value inputReal = rewriter.create(loc, ifft1DAdaptor.getReal(), ValueRange{ivX}); - Value loadYReal = rewriter.create(loc, alloc_real, ValueRange{ivY}); - - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value k = rewriter.create(loc, rewriter.getF64Type(), IndxY); - - Value IndxX = rewriter.create(loc, rewriter.getIntegerType(32), ivX); - Value i = rewriter.create(loc, rewriter.getF64Type(), IndxX); - - //get 2*pi * k * i / N - Value muli_k = rewriter.create(loc, k , i); - - Value const2pi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(6.28318530718)); - Value mul2piKI = rewriter.create(loc, const2pi , muli_k); + Value inputReal = rewriter.create( + loc, ifft1DAdaptor.getReal(), ValueRange{ivX}); + Value loadYReal = + rewriter.create(loc, alloc_real, ValueRange{ivY}); + + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value k = + rewriter.create(loc, rewriter.getF64Type(), IndxY); + + Value IndxX = rewriter.create( + loc, rewriter.getIntegerType(32), ivX); + Value i = + rewriter.create(loc, rewriter.getF64Type(), IndxX); + + // get 2*pi * k * i / N + Value muli_k = rewriter.create(loc, k, i); + + Value const2pi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(6.28318530718)); + Value mul2piKI = rewriter.create(loc, const2pi, muli_k); // getOperand().getType() - // auto inputTensorType = llvm::cast(op->getOperand(0).getType()); - float LengthOfInput = (float) ub; - Value N = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(LengthOfInput)); + // auto inputTensorType = + // llvm::cast(op->getOperand(0).getType()); + float LengthOfInput = (float)ub; + Value N = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(LengthOfInput)); // Value N = inputTensorType.getShape()[0]; - Value divIndxByN = rewriter.create(loc, mul2piKI, N ) ; + Value divIndxByN = rewriter.create(loc, mul2piKI, N); - // Real Cos part = x_real[i] * cos(div) + // Real Cos part = x_real[i] * cos(div) Value GetCos = rewriter.create(loc, divIndxByN); - Value xMulCos = rewriter.create(loc, inputReal , GetCos); + Value xMulCos = rewriter.create(loc, inputReal, GetCos); - // Real Sin part = x_complex[i] * sin(div) - Value inputImg = rewriter.create(loc, ifft1DAdaptor.getImg(), ValueRange{ivX}); + // Real Sin part = x_complex[i] * sin(div) + Value inputImg = rewriter.create(loc, ifft1DAdaptor.getImg(), + ValueRange{ivX}); Value GetSin = rewriter.create(loc, divIndxByN); - Value xMulSin = rewriter.create(loc, inputImg , GetSin); - - //Get real Ans = x_real[i] * cos(div) - x_complex[i] * sin(div) - //Then sum over real_Ans by loading YReal - Value realAns = rewriter.create(loc, xMulCos ,xMulSin) ; - Value realSum = rewriter.create(loc, loadYReal ,realAns) ; - rewriter.create(loc, realSum, alloc_real, ValueRange{ivY}); - - //x[n-1] - DEBUG_PRINT_NO_ARGS() ; + Value xMulSin = rewriter.create(loc, inputImg, GetSin); + + // Get real Ans = x_real[i] * cos(div) - x_complex[i] * sin(div) + // Then sum over real_Ans by loading YReal + Value realAns = rewriter.create(loc, xMulCos, xMulSin); + Value realSum = rewriter.create(loc, loadYReal, realAns); + rewriter.create(loc, realSum, alloc_real, ValueRange{ivY}); + + // x[n-1] + DEBUG_PRINT_NO_ARGS(); // Value xMinusPrevX = rewriter.create(loc, inputX ,PrevX ); rewriter.setInsertionPointAfter(forOpX); // Calculate y[k] = 1/N * y[k] - Value loadY = rewriter.create(loc, alloc_real, ValueRange{ivY}); + Value loadY = + rewriter.create(loc, alloc_real, ValueRange{ivY}); // float LengthOfInput = (float) ub; - Value N1 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(LengthOfInput)); - Value SumDivByN = rewriter.create(loc,loadY , N1 ); - rewriter.create(loc, SumDivByN, alloc_real, ValueRange{ivY}); - + Value N1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(LengthOfInput)); + Value SumDivByN = rewriter.create(loc, loadY, N1); + rewriter.create(loc, SumDivByN, alloc_real, ValueRange{ivY}); rewriter.setInsertionPointAfter(forOpY); - //debug - // forOpX->dump(); - // forOpY->dump(); - // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - - - // affine.for %y = 0 to 4 { - // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> - // // affine.store %0, %alloc_real[%arg0] : memref<4xf64> - // affine.for %x = 0 to 4 { - // // CAcluations - // %1 = affine.load %alloc_3[%x] : memref<4xf64> - // %2 = affine.load %alloc_real[%y] : memref<4xf64> - // %3 = affine.load %alloc_img[%y] : memref<4xf64> - // // index cast for multiply - // %4 = arith.index_castui %y : index to i32 - // %k = arith.uitofp %4 : i32 to f64 - // %6 = arith.index_castui %x : index to i32 - // %i = arith.uitofp %6 : i32 to f64 - // // %8 = arith.index_castui %arg3 : index to i32 - // // %9 = arith.uitofp %8 : i32 to f64 - // // %10 = arith.index_castui %arg4 : index to i32 - // // %11 = arith.uitofp %10 : i32 to f64 - - // %mul_1 = arith.mulf %i, %k : f64 - // %mul = arith.mulf %mul_1, %cst_2pi : f64 - // // ixk / N - // %div = arith.divf %mul, %N : f64 - // // cos of the above - // %res_cos = math.cos %div : f64 - // // %16 = arith.addf %14, %15 : f64 - // // %res_sin = arith.mulf %16, %cst_0 : f64 - - // %res_sin = math.sin %div : f64 - // %real_prod = arith.mulf %1, %res_cos : f64 - // %img_prod_1 = arith.mulf %1, %res_sin : f64 - // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 - - // %real = arith.addf %2, %real_prod : f64 - // %img = arith.addf %3, %img_prod : f64 - // affine.store %real, %alloc_real[%y] : memref<4xf64> - // // dsp.print %alloc_real : memref<4xf64> - // affine.store %img, %alloc_img[%y] : memref<4xf64> - - // } - // } + // debug + // forOpX->dump(); + // forOpY->dump(); + // affine.for %y = 0 to 4 { + // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + + // affine.for %y = 0 to 4 { + // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> + // // affine.store %0, %alloc_real[%arg0] : memref<4xf64> + // affine.for %x = 0 to 4 { + // // CAcluations + // %1 = affine.load %alloc_3[%x] : memref<4xf64> + // %2 = affine.load %alloc_real[%y] : memref<4xf64> + // %3 = affine.load %alloc_img[%y] : memref<4xf64> + // // index cast for multiply + // %4 = arith.index_castui %y : index to i32 + // %k = arith.uitofp %4 : i32 to f64 + // %6 = arith.index_castui %x : index to i32 + // %i = arith.uitofp %6 : i32 to f64 + // // %8 = arith.index_castui %arg3 : index to i32 + // // %9 = arith.uitofp %8 : i32 to f64 + // // %10 = arith.index_castui %arg4 : index to i32 + // // %11 = arith.uitofp %10 : i32 to f64 + + // %mul_1 = arith.mulf %i, %k : f64 + // %mul = arith.mulf %mul_1, %cst_2pi : f64 + // // ixk / N + // %div = arith.divf %mul, %N : f64 + // // cos of the above + // %res_cos = math.cos %div : f64 + // // %16 = arith.addf %14, %15 : f64 + // // %res_sin = arith.mulf %16, %cst_0 : f64 + + // %res_sin = math.sin %div : f64 + // %real_prod = arith.mulf %1, %res_cos : f64 + // %img_prod_1 = arith.mulf %1, %res_sin : f64 + // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 + + // %real = arith.addf %2, %real_prod : f64 + // %img = arith.addf %3, %img_prod : f64 + // affine.store %real, %alloc_real[%y] : memref<4xf64> + // // dsp.print %alloc_real : memref<4xf64> + // affine.store %img, %alloc_img[%y] : memref<4xf64> + + // } + // } rewriter.replaceOp(op, alloc_real); // rewriter.replaceOp(op, ValueRange{alloc_real,alloc_img}); - + return success(); } }; - - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: FFT1D operations //===----------------------------------------------------------------------===// - struct FFT1DOpLowering : public ConversionPattern { FFT1DOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::FFT1DOp::getOperationName(), 1, ctx) {} @@ -5305,173 +5607,187 @@ struct FFT1DOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - // y[k] = y_real[k] + j *y_img[k] - // y_real = sumOver_n(x[n]*cos[2*pi * k *n/N ] - // y_img = sumOver_n(x[n]*sin[2*pi * k *n/N ] * -1 - //init output mem for y_real & y_img as 0 - //iterate for output from k=0 to last - //iterate for all x from n=0 to last - //perform the calculations : ie x[n] * cos[2*pi * k *n/N ] and sum and store them at y[k] - // - // replace this upsampling op with the output_mem_allocation op + + // Pseudo-code: + // y[k] = y_real[k] + j *y_img[k] + // y_real = sumOver_n(x[n]*cos[2*pi * k *n/N ] + // y_img = sumOver_n(x[n]*sin[2*pi * k *n/N ] * -1 + // init output mem for y_real & y_img as 0 + // iterate for output from k=0 to last + // iterate for all x from n=0 to last + // perform the calculations : ie x[n] * cos[2*pi * k *n/N ] and sum and + // store them at y[k] + // + // replace this upsampling op with the output_mem_allocation op // DEBUG_PRINT_NO_ARGS() ; - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - //iterate to result1 --not needed for now but for future reference - // auto tensorType1 = llvm::cast(*std::next(op->result_type_begin(), 1)); + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + // iterate to result1 --not needed for now but for future reference + // auto tensorType1 = + // llvm::cast(*std::next(op->result_type_begin(), 1)); + + // DEBUG_PRINT_NO_ARGS() ; + // tensorType.getShape()[0] + // llvm::errs() << "tensorType1.getShape()[0] " << tensorType1.getShape()[0] + // << " func= " << __func__ << "\n"; - // DEBUG_PRINT_NO_ARGS() ; - //tensorType.getShape()[0] - // llvm::errs() << "tensorType1.getShape()[0] " << tensorType1.getShape()[0] << " func= " << __func__ << "\n"; - - //allocation & deallocation for the result of this operation + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); // auto memRefType2 = convertTensorToMemRef(tensorType1); auto alloc_real = insertAllocAndDealloc(memRefType, loc, rewriter); auto alloc_img = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); - + // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - //For loop -- iterate from 1 to last - int64_t lb = 0 ; + // For loop -- iterate from 1 to last + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); rewriter.create(loc, constant0, alloc_real, ValueRange{iv}); rewriter.create(loc, constant0, alloc_img, ValueRange{iv}); rewriter.setInsertionPointAfter(forOp1); - //loop for Y - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + // loop for Y + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - //loop for X - affine::AffineForOp forOpX = rewriter.create(loc, lb, ub, step); + // loop for X + affine::AffineForOp forOpX = + rewriter.create(loc, lb, ub, step); auto ivX = forOpX.getInductionVar(); rewriter.setInsertionPointToStart(forOpX.getBody()); - //load from X, & y1 & y2 + // load from X, & y1 & y2 FFT1DOpAdaptor fft1DAdaptor(operands); - Value inputX = rewriter.create(loc, fft1DAdaptor.getInput(), ValueRange{ivX}); - Value loadYReal = rewriter.create(loc, alloc_real, ValueRange{ivY}); - Value loadYImg = rewriter.create(loc, alloc_img, ValueRange{ivY}); - - //convert index to f64 - Value IndxY = rewriter.create(loc, rewriter.getIntegerType(32), ivY); - Value k = rewriter.create(loc, rewriter.getF64Type(), IndxY); - - Value IndxX = rewriter.create(loc, rewriter.getIntegerType(32), ivX); - Value i = rewriter.create(loc, rewriter.getF64Type(), IndxX); - - //get 2*pi * k * i / N - Value muli_k = rewriter.create(loc, k , i); - - Value const2pi = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(6.28318530718)); - Value mul2piKI = rewriter.create(loc, const2pi , muli_k); + Value inputX = rewriter.create(loc, fft1DAdaptor.getInput(), + ValueRange{ivX}); + Value loadYReal = + rewriter.create(loc, alloc_real, ValueRange{ivY}); + Value loadYImg = + rewriter.create(loc, alloc_img, ValueRange{ivY}); + + // convert index to f64 + Value IndxY = rewriter.create( + loc, rewriter.getIntegerType(32), ivY); + Value k = + rewriter.create(loc, rewriter.getF64Type(), IndxY); + + Value IndxX = rewriter.create( + loc, rewriter.getIntegerType(32), ivX); + Value i = + rewriter.create(loc, rewriter.getF64Type(), IndxX); + + // get 2*pi * k * i / N + Value muli_k = rewriter.create(loc, k, i); + + Value const2pi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(6.28318530718)); + Value mul2piKI = rewriter.create(loc, const2pi, muli_k); // getOperand().getType() - // auto inputTensorType = llvm::cast(op->getOperand(0).getType()); - float LengthOfInput = (float) ub; - Value N = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(LengthOfInput)); + // auto inputTensorType = + // llvm::cast(op->getOperand(0).getType()); + float LengthOfInput = (float)ub; + Value N = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(LengthOfInput)); // Value N = inputTensorType.getShape()[0]; - Value divIndxByN = rewriter.create(loc, mul2piKI, N ) ; + Value divIndxByN = rewriter.create(loc, mul2piKI, N); // Real part = Sum(x[i] * cos(div) ) Value GetCos = rewriter.create(loc, divIndxByN); - Value xMulCos = rewriter.create(loc, inputX , GetCos); - Value realSum = rewriter.create(loc, loadYReal ,xMulCos) ; - rewriter.create(loc, realSum, alloc_real, ValueRange{ivY}); - + Value xMulCos = rewriter.create(loc, inputX, GetCos); + Value realSum = rewriter.create(loc, loadYReal, xMulCos); + rewriter.create(loc, realSum, alloc_real, ValueRange{ivY}); + // Img part = -1 * Sum(x[i] * sin(div) ) Value GetSin = rewriter.create(loc, divIndxByN); - Value xMulSin = rewriter.create(loc, inputX , GetSin); - Value imgSum = rewriter.create(loc, loadYImg ,xMulSin) ; + Value xMulSin = rewriter.create(loc, inputX, GetSin); + Value imgSum = rewriter.create(loc, loadYImg, xMulSin); - // Value constMinus1 = rewriter.create(loc, rewriter.getF64Type(), + // Value constMinus1 = rewriter.create(loc, + // rewriter.getF64Type(), // rewriter.getF64FloatAttr(-1)); - // Value NegImgSum = rewriter.create(loc, constMinus1 , imgSum); - rewriter.create(loc, imgSum, alloc_img, ValueRange{ivY}); - //x[n-1] - // DEBUG_PRINT_NO_ARGS() ; - // Value xMinusPrevX = rewriter.create(loc, inputX ,PrevX ); + // Value NegImgSum = rewriter.create(loc, constMinus1 , + // imgSum); + rewriter.create(loc, imgSum, alloc_img, ValueRange{ivY}); + // x[n-1] + // DEBUG_PRINT_NO_ARGS() ; + // Value xMinusPrevX = rewriter.create(loc, inputX ,PrevX ); rewriter.setInsertionPointAfter(forOpX); // forOpX->dump(); // rewriter.create(loc, ValueRange{alloc_real, alloc_img}); rewriter.setInsertionPointAfter(forOpY); - //debug - // forOpX->dump(); - // forOpY->dump(); - // affine.for %y = 0 to 4 { - // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> - // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> - // } - - - // affine.for %y = 0 to 4 { - // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> - // // affine.store %0, %alloc_real[%arg0] : memref<4xf64> - // affine.for %x = 0 to 4 { - // // CAcluations - // %1 = affine.load %alloc_3[%x] : memref<4xf64> - // %2 = affine.load %alloc_real[%y] : memref<4xf64> - // %3 = affine.load %alloc_img[%y] : memref<4xf64> - // // index cast for multiply - // %4 = arith.index_castui %y : index to i32 - // %k = arith.uitofp %4 : i32 to f64 - // %6 = arith.index_castui %x : index to i32 - // %i = arith.uitofp %6 : i32 to f64 - // // %8 = arith.index_castui %arg3 : index to i32 - // // %9 = arith.uitofp %8 : i32 to f64 - // // %10 = arith.index_castui %arg4 : index to i32 - // // %11 = arith.uitofp %10 : i32 to f64 - - // %mul_1 = arith.mulf %i, %k : f64 - // %mul = arith.mulf %mul_1, %cst_2pi : f64 - // // ixk / N - // %div = arith.divf %mul, %N : f64 - // // cos of the above - // %res_cos = math.cos %div : f64 - // // %16 = arith.addf %14, %15 : f64 - // // %res_sin = arith.mulf %16, %cst_0 : f64 - - // %res_sin = math.sin %div : f64 - // %real_prod = arith.mulf %1, %res_cos : f64 - // %img_prod_1 = arith.mulf %1, %res_sin : f64 - // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 - - // %real = arith.addf %2, %real_prod : f64 - // %img = arith.addf %3, %img_prod : f64 - // affine.store %real, %alloc_real[%y] : memref<4xf64> - // // dsp.print %alloc_real : memref<4xf64> - // affine.store %img, %alloc_img[%y] : memref<4xf64> - - // } - // } + // debug + // forOpX->dump(); + // forOpY->dump(); + // affine.for %y = 0 to 4 { + // affine.store %cst_3, %alloc_real[%y] : memref<4xf64> + // affine.store %cst_3, %alloc_img[%y] : memref<4xf64> + // } + + // affine.for %y = 0 to 4 { + // // %0 = affine.load %alloc_3[%arg0] : memref<4xf64> + // // affine.store %0, %alloc_real[%arg0] : memref<4xf64> + // affine.for %x = 0 to 4 { + // // CAcluations + // %1 = affine.load %alloc_3[%x] : memref<4xf64> + // %2 = affine.load %alloc_real[%y] : memref<4xf64> + // %3 = affine.load %alloc_img[%y] : memref<4xf64> + // // index cast for multiply + // %4 = arith.index_castui %y : index to i32 + // %k = arith.uitofp %4 : i32 to f64 + // %6 = arith.index_castui %x : index to i32 + // %i = arith.uitofp %6 : i32 to f64 + // // %8 = arith.index_castui %arg3 : index to i32 + // // %9 = arith.uitofp %8 : i32 to f64 + // // %10 = arith.index_castui %arg4 : index to i32 + // // %11 = arith.uitofp %10 : i32 to f64 + + // %mul_1 = arith.mulf %i, %k : f64 + // %mul = arith.mulf %mul_1, %cst_2pi : f64 + // // ixk / N + // %div = arith.divf %mul, %N : f64 + // // cos of the above + // %res_cos = math.cos %div : f64 + // // %16 = arith.addf %14, %15 : f64 + // // %res_sin = arith.mulf %16, %cst_0 : f64 + + // %res_sin = math.sin %div : f64 + // %real_prod = arith.mulf %1, %res_cos : f64 + // %img_prod_1 = arith.mulf %1, %res_sin : f64 + // %img_prod = arith.mulf %cst_5, %img_prod_1 : f64 + + // %real = arith.addf %2, %real_prod : f64 + // %img = arith.addf %3, %img_prod : f64 + // affine.store %real, %alloc_real[%y] : memref<4xf64> + // // dsp.print %alloc_real : memref<4xf64> + // affine.store %img, %alloc_img[%y] : memref<4xf64> + + // } + // } // rewriter.replaceOp(op, alloc_real); - rewriter.replaceOp(op, ValueRange{alloc_real,alloc_img}); - + rewriter.replaceOp(op, ValueRange{alloc_real, alloc_img}); + return success(); } }; @@ -5480,7 +5796,6 @@ struct FFT1DOpLowering : public ConversionPattern { // ToyToAffine RewritePatterns: HighPassFilter operations //===----------------------------------------------------------------------===// - struct HighPassFilterOpLowering : public ConversionPattern { HighPassFilterOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::HighPassFilterOp::getOperationName(), 1, ctx) {} @@ -5489,83 +5804,88 @@ struct HighPassFilterOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - //init first value of output with first value of input: y[0] = x[0] - //iterate for output from 1st to last - //y[i] = x[i] - x[i -1 ] - // replace this upsampling op with the output_mem_allocation op - - DEBUG_PRINT_NO_ARGS() ; - - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + + // Pseudo-code: + // init first value of output with first value of input: y[0] = x[0] + // iterate for output from 1st to last + // y[i] = x[i] - x[i -1 ] + // replace this upsampling op with the output_mem_allocation op + + DEBUG_PRINT_NO_ARGS(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); - //Init y for the first index ie, index0 + // Init y for the first index ie, index0 Value constantIndx0 = rewriter.create(loc, 0); HighPassFilterOpAdaptor highPassFilterAdaptor(operands); - Value GetInputX0 = rewriter.create(loc, highPassFilterAdaptor.getInput(), /* iv */ ValueRange{constantIndx0}); - rewriter.create(loc, GetInputX0, alloc, ValueRange{constantIndx0}); - - //For loop -- iterate from 1 to last - int64_t lb = 1 ; + Value GetInputX0 = + rewriter.create(loc, highPassFilterAdaptor.getInput(), + /* iv */ ValueRange{constantIndx0}); + rewriter.create(loc, GetInputX0, alloc, + ValueRange{constantIndx0}); + + // For loop -- iterate from 1 to last + int64_t lb = 1; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv = forOp1.getInductionVar(); - rewriter.setInsertionPointToStart(forOp1.getBody()); - - - //For affine expression: #map1 = affine_map<(%arg0)[] : (%arg0 - 1) + // For affine expression: #map1 = affine_map<(%arg0)[] : (%arg0 - 1) AffineExpr d0, s0; bindDims(rewriter.getContext(), d0); AffineExpr ExprForPrevX = d0 - 1; AffineMap addMapForHighPassFilter = AffineMap::get(1, 0, ExprForPrevX); - //x[n-1] - DEBUG_PRINT_NO_ARGS() ; - Value PrevX = rewriter.create(loc, highPassFilterAdaptor.getInput(), addMapForHighPassFilter, - ValueRange{iv}); //memRefType + // x[n-1] + DEBUG_PRINT_NO_ARGS(); + Value PrevX = rewriter.create( + loc, highPassFilterAdaptor.getInput(), addMapForHighPassFilter, + ValueRange{iv}); // memRefType // PrevX.dump(); - Value inputX = rewriter.create(loc, highPassFilterAdaptor.getInput(), ValueRange{iv}); - - //get y[i] = x[i] - x[i -1 ] - Value xMinusPrevX = rewriter.create(loc, inputX ,PrevX ); + Value inputX = rewriter.create( + loc, highPassFilterAdaptor.getInput(), ValueRange{iv}); + + // get y[i] = x[i] - x[i -1 ] + Value xMinusPrevX = rewriter.create(loc, inputX, PrevX); // Value cosRes = rewriter.create(loc, xMinusPrevX); - rewriter.create(loc, xMinusPrevX, alloc, ValueRange{iv}); //PrevX //AddmulAlphaXAndPreYAlphaMinus1 + rewriter.create( + loc, xMinusPrevX, alloc, + ValueRange{iv}); // PrevX //AddmulAlphaXAndPreYAlphaMinus1 rewriter.setInsertionPointAfter(forOp1); - //debug - // forOp1->dump(); - // init first value of output with first value of input: y[0] = x[0] - // iterate for output from 1st to last - // y[i] = x[i] - x[i -1 ] - // replace this upsampling op with the output_mem_allocation op - // %indx0 = arith.constantIndex 0 : index - // %0 = affine.load in[indx0 ] : f64 - // affine.store %0 ,out[indx0] - // affine.for %arg0 = 1 to len_y { - // #map1 = affine_map<(%arg0)[] : (%arg0 - 1) - // %1 = affine.load in[#map1] - // %load_in = affine.load in[%arg0] - // %2 = arith.subf %const1 , alpha - // affine.store %2, out[%arg0] - // } + // debug + // forOp1->dump(); + // init first value of output with first value of input: y[0] = x[0] + // iterate for output from 1st to last + // y[i] = x[i] - x[i -1 ] + // replace this upsampling op with the output_mem_allocation op + // %indx0 = arith.constantIndex 0 : index + // %0 = affine.load in[indx0 ] : f64 + // affine.store %0 ,out[indx0] + // affine.for %arg0 = 1 to len_y { + // #map1 = affine_map<(%arg0)[] : (%arg0 - 1) + // %1 = affine.load in[#map1] + // %load_in = affine.load in[%arg0] + // %2 = arith.subf %const1 , alpha + // affine.store %2, out[%arg0] + // } rewriter.replaceOp(op, alloc); - + return success(); } }; @@ -5574,127 +5894,134 @@ struct HighPassFilterOpLowering : public ConversionPattern { // ToyToAffine RewritePatterns: LowPassFilter operations //===----------------------------------------------------------------------===// - struct LowPassFilter1stOrderOpLowering : public ConversionPattern { LowPassFilter1stOrderOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::LowPassFilter1stOrderOp::getOperationName(), 1, ctx) {} + : ConversionPattern(dsp::LowPassFilter1stOrderOp::getOperationName(), 1, + ctx) {} LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - //init first value of output with first value of input: y[0] = x[0] - //iterate for output from 1st to last - //y[i] = (1 - alpha) * y[i-1] + alpha * x[i] - // replace this upsampling op with the output_mem_allocation op + + // Pseudo-code: + // init first value of output with first value of input: y[0] = x[0] + // iterate for output from 1st to last + // y[i] = (1 - alpha) * y[i-1] + alpha * x[i] + // replace this upsampling op with the output_mem_allocation op // DEBUG_PRINT_NO_ARGS() ; - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); - //Init y for the first index ie, index0 + // Init y for the first index ie, index0 Value constantIndx0 = rewriter.create(loc, 0); LowPassFilter1stOrderOpAdaptor lowPassFilterAdaptor(operands); - Value GetInputX0 = rewriter.create(loc, lowPassFilterAdaptor.getLhs(), /* iv */ ValueRange{constantIndx0}); - rewriter.create(loc, GetInputX0, alloc, ValueRange{constantIndx0}); + Value GetInputX0 = rewriter.create( + loc, lowPassFilterAdaptor.getLhs(), /* iv */ ValueRange{constantIndx0}); + rewriter.create(loc, GetInputX0, alloc, + ValueRange{constantIndx0}); - //For loop -- iterate from 1 to last - int64_t lb = 1 ; + // For loop -- iterate from 1 to last + int64_t lb = 1; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv = forOp1.getInductionVar(); - rewriter.setInsertionPointToStart(forOp1.getBody()); - - - //For affine expression: #map1 = affine_map<(%arg0)[] : (%arg0 - 1) + // For affine expression: #map1 = affine_map<(%arg0)[] : (%arg0 - 1) AffineExpr d0, s0; bindDims(rewriter.getContext(), d0); AffineExpr ExprForPrevY = d0 - 1; AffineMap addMapForLowPassFilter = AffineMap::get(1, 0, ExprForPrevY); - //y[n-1] - // DEBUG_PRINT_NO_ARGS() ; - // Value PrevY = rewriter.create(loc, lowPassFilterAdaptor.getLhs(), addMapForLowPassFilter, - // ValueRange{iv}); - // Value PrevY = rewriter.create(loc, (*op->result_type_begin()), addMapForLowPassFilter, - // ValueRange{iv}); //memRefType - Value PrevY = rewriter.create(loc, alloc, addMapForLowPassFilter, - ValueRange{iv}); //memRefType + // y[n-1] + // DEBUG_PRINT_NO_ARGS() ; + // Value PrevY = rewriter.create(loc, + // lowPassFilterAdaptor.getLhs(), addMapForLowPassFilter, + // ValueRange{iv}); + // Value PrevY = rewriter.create(loc, + // (*op->result_type_begin()), addMapForLowPassFilter, + // ValueRange{iv}); //memRefType + Value PrevY = rewriter.create( + loc, alloc, addMapForLowPassFilter, ValueRange{iv}); // memRefType // PrevY.dump(); - Value constant1 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + Value constant1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); // Value alpha = lowPassFilterAdaptor.getRhs(); //op->getOperand(1); - Value alpha = rewriter.create(loc, lowPassFilterAdaptor.getRhs(), /* iv */ ValueRange{}); - //get y[n] = (1- alpha ) * y[n-1] + alpha * x[n] - Value oneMinusAlpha = rewriter.create(loc, constant1 ,alpha ); - Value mulPrevYAlphaMinus1 = rewriter.create(loc, oneMinusAlpha ,PrevY); - - Value inputX = rewriter.create(loc, lowPassFilterAdaptor.getLhs(), ValueRange{iv}); - Value mulAlphaX = rewriter.create(loc, alpha ,inputX); - - Value AddmulAlphaXAndPreYAlphaMinus1 = rewriter.create(loc, mulPrevYAlphaMinus1 ,mulAlphaX); + Value alpha = rewriter.create( + loc, lowPassFilterAdaptor.getRhs(), /* iv */ ValueRange{}); + // get y[n] = (1- alpha ) * y[n-1] + alpha * x[n] + Value oneMinusAlpha = rewriter.create(loc, constant1, alpha); + Value mulPrevYAlphaMinus1 = + rewriter.create(loc, oneMinusAlpha, PrevY); + + Value inputX = rewriter.create( + loc, lowPassFilterAdaptor.getLhs(), ValueRange{iv}); + Value mulAlphaX = rewriter.create(loc, alpha, inputX); + + Value AddmulAlphaXAndPreYAlphaMinus1 = + rewriter.create(loc, mulPrevYAlphaMinus1, mulAlphaX); // DEBUG_PRINT_NO_ARGS() ; // AddmulAlphaXAndPreYAlphaMinus1.dump(); // forOp1->dump(); - rewriter.create(loc, AddmulAlphaXAndPreYAlphaMinus1, alloc, ValueRange{iv}); //PrevY //AddmulAlphaXAndPreYAlphaMinus1 + rewriter.create( + loc, AddmulAlphaXAndPreYAlphaMinus1, alloc, + ValueRange{iv}); // PrevY //AddmulAlphaXAndPreYAlphaMinus1 rewriter.setInsertionPointAfter(forOp1); - //debug - // forOp1->dump(); - // init first value of output with first value of input: y[0] = x[0] - // iterate for output from 1st to last - // y[i] = (1 - alpha) * y[i-1] + alpha * x[i] - // replace this upsampling op with the output_mem_allocation op - // %indx0 = arith.constantIndex 0 : index - // %0 = affine.load in[indx0 ] : f64 - // affine.store %0 ,out[indx0] - // affine.for %arg0 = 1 to len_y { - // #map1 = affine_map<(%arg0)[] : (%arg0 - 1) - // %1 = affine.load out[#map1] - // %2 = arith.subf %const1 , alpha - // %3 = arith.mulf %2 , %1 - - // %load_in = affine.load in[%arg0] - // %4 = arith.mulf alpha, %load_in - // %5 = arith.addf %4, %3 - // affine.store %5, out[%arg0] - // } - // %2ndOperand = arith.const 3 : f64 - // affine.for %arg0 = 0 to input_len { - // %elem1 = affine.load input[%arg0] <-- affine apply - // #map1 = affine_map<(%arg0)[2ndOperand] : (%arg0 * 2ndOperand) - // - // affine.store %elem1, out[#map1] - // } + // debug + // forOp1->dump(); + // init first value of output with first value of input: y[0] = x[0] + // iterate for output from 1st to last + // y[i] = (1 - alpha) * y[i-1] + alpha * x[i] + // replace this upsampling op with the output_mem_allocation op + // %indx0 = arith.constantIndex 0 : index + // %0 = affine.load in[indx0 ] : f64 + // affine.store %0 ,out[indx0] + // affine.for %arg0 = 1 to len_y { + // #map1 = affine_map<(%arg0)[] : (%arg0 - 1) + // %1 = affine.load out[#map1] + // %2 = arith.subf %const1 , alpha + // %3 = arith.mulf %2 , %1 + + // %load_in = affine.load in[%arg0] + // %4 = arith.mulf alpha, %load_in + // %5 = arith.addf %4, %3 + // affine.store %5, out[%arg0] + // } + // %2ndOperand = arith.const 3 : f64 + // affine.for %arg0 = 0 to input_len { + // %elem1 = affine.load input[%arg0] <-- affine apply + // #map1 = affine_map<(%arg0)[2ndOperand] : (%arg0 * 2ndOperand) + // + // affine.store %elem1, out[#map1] + // } rewriter.replaceOp(op, alloc); - + return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: Upsampling operations //===----------------------------------------------------------------------===// - struct UpSamplingOpLowering : public ConversionPattern { UpSamplingOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::UpsamplingOp::getOperationName(), 1, ctx) {} @@ -5703,111 +6030,124 @@ struct UpSamplingOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - //init all out values with 0 using affine loop - //Update certain y_values with corresponding x - //iterate for input : i = 0 to len - //get the corresponding output mapping index = M * i - // store in y at that index - // replace this upsampling op with the output_mem_allocation op + + // Pseudo-code: + // init all out values with 0 using affine loop + // Update certain y_values with corresponding x + // iterate for input : i = 0 to len + // get the corresponding output mapping index = M * i + // store in y at that index + // replace this upsampling op with the output_mem_allocation op // DEBUG_PRINT_NO_ARGS() ; - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); - //For loop - int64_t lb = 0 ; + // For loop + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - //init all the output mem location with 0 - affine::AffineForOp forOpSetOut0Loop = rewriter.create(loc, lb, ub, step); + // init all the output mem location with 0 + affine::AffineForOp forOpSetOut0Loop = + rewriter.create(loc, lb, ub, step); auto ivforOpSetOut0Loop = forOpSetOut0Loop.getInductionVar(); - rewriter.setInsertionPointToStart(forOpSetOut0Loop.getBody()); - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - //store the result + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + // store the result rewriter.create(loc, constant0, alloc, ivforOpSetOut0Loop); rewriter.setInsertionPointAfter(forOpSetOut0Loop); Value upsampling2ndArg = op->getOperand(1); UpsamplingOpAdaptor upsamplingAdaptor(operands); - auto inputType = llvm::dyn_cast(op->getOperand(0).getType()); - int64_t ub2 = inputType.getShape()[0]; // tensorType.getShape()[0]; - //create another for loop for updating corresponding y with x - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub2, step); + auto inputType = + llvm::dyn_cast(op->getOperand(0).getType()); + int64_t ub2 = inputType.getShape()[0]; // tensorType.getShape()[0]; + // create another for loop for updating corresponding y with x + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub2, step); auto iv = forOp1.getInductionVar(); - rewriter.setInsertionPointToStart(forOp1.getBody()); - //Load input elem - - Value elemIn = rewriter.create(loc, upsamplingAdaptor.getLhs(), iv); + // Load input elem - // Value elemIn = rewriter.create(loc, upsamplingAdaptor.getLhs(), addMapForUpSampling, + Value elemIn = + rewriter.create(loc, upsamplingAdaptor.getLhs(), iv); + + // Value elemIn = rewriter.create(loc, + // upsamplingAdaptor.getLhs(), addMapForUpSampling, // ValueRange{iv,constantSamplingRateIndx}); - - - //For affine expression: #map1 = affine_map<(%arg0)[2ndOperand] : (%arg0 * 2ndOperand) + // For affine expression: #map1 = affine_map<(%arg0)[2ndOperand] : (%arg0 * + // 2ndOperand) AffineExpr d0, s0; bindDims(rewriter.getContext(), d0); bindSymbols(rewriter.getContext(), s0); - // AffineExpr ExprForUpSampling = rewriter.getAffineDimExpr(0) * rewriter.getAffineSymbolExpr(0); + // AffineExpr ExprForUpSampling = rewriter.getAffineDimExpr(0) * + // rewriter.getAffineSymbolExpr(0); AffineExpr ExprForUpSampling = d0 * s0; - // Value constant3 = rewriter.create(loc, rewriter.getI64Type(), rewriter.getIntegerAttr(rewriter.getIntegerType(64), 3)); - Value constant3 = rewriter.create(loc, 3); //working + // Value constant3 = rewriter.create(loc, + // rewriter.getI64Type(), + // rewriter.getIntegerAttr(rewriter.getIntegerType(64), 3)); + Value constant3 = + rewriter.create(loc, 3); // working constant3.dump(); int64_t SecondValueInt = 1; - - dsp::ConstantOp constantOp2ndArg = upsampling2ndArg.getDefiningOp(); - DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue();; + + dsp::ConstantOp constantOp2ndArg = + upsampling2ndArg.getDefiningOp(); + DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue(); + ; auto elements = constantRhsValue.getValues(); float SecondValue = elements[0].getValueAsDouble(); - SecondValueInt = (int64_t) SecondValue; + SecondValueInt = (int64_t)SecondValue; - // Value downSamplingRateAsIndex = rewriter.create(loc, rewriter.getIndexType(),UpsamplingRate); - Value constantSamplingRateIndx = rewriter.create(loc, SecondValueInt); + // Value downSamplingRateAsIndex = rewriter.create(loc, + // rewriter.getIndexType(),UpsamplingRate); + Value constantSamplingRateIndx = + rewriter.create(loc, SecondValueInt); constantSamplingRateIndx.dump(); - + AffineMap addMapForUpSampling = AffineMap::get(1, 1, ExprForUpSampling); // DEBUG_PRINT_NO_ARGS() ; - // Value elem2 = rewriter.create(loc, upsamplingAdaptor.getLhs(), addMapForUpSampling, + // Value elem2 = rewriter.create(loc, + // upsamplingAdaptor.getLhs(), addMapForUpSampling, // ValueRange{iv,constantSamplingRateIndx}); // elem2.dump(); - //store the result - rewriter.create(loc, elemIn, alloc, addMapForUpSampling, ValueRange{iv,constantSamplingRateIndx}); + // store the result + rewriter.create(loc, elemIn, alloc, addMapForUpSampling, + ValueRange{iv, constantSamplingRateIndx}); rewriter.setInsertionPointAfter(forOp1); - //debug - // forOp1->dump(); - // %0 = arith.const 0 : f64 - // affine.for %arg0 = 0 to out_y { - // affine.store %0, out[%arg0] - // } - // %2ndOperand = arith.const 3 : f64 - // affine.for %arg0 = 0 to input_len { - // %elem1 = affine.load input[%arg0] <-- affine apply - // #map1 = affine_map<(%arg0)[2ndOperand] : (%arg0 * 2ndOperand) - // - // affine.store %elem1, out[#map1] - // } + // debug + // forOp1->dump(); + // %0 = arith.const 0 : f64 + // affine.for %arg0 = 0 to out_y { + // affine.store %0, out[%arg0] + // } + // %2ndOperand = arith.const 3 : f64 + // affine.for %arg0 = 0 to input_len { + // %elem1 = affine.load input[%arg0] <-- affine apply + // #map1 = affine_map<(%arg0)[2ndOperand] : (%arg0 * 2ndOperand) + // + // affine.store %elem1, out[#map1] + // } rewriter.replaceOp(op, alloc); - + return success(); } }; @@ -5816,7 +6156,6 @@ struct UpSamplingOpLowering : public ConversionPattern { // ToyToAffine RewritePatterns: Downsampling operations //===----------------------------------------------------------------------===// - struct DownSamplingOpLowering : public ConversionPattern { DownSamplingOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::DownsamplingOp::getOperationName(), 1, ctx) {} @@ -5825,83 +6164,94 @@ struct DownSamplingOpLowering : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - //iterate for output len : i = 0 to len - //get the input elem using input mapping index = M* i - // store in y - // replace this op with the output_mem + + // Pseudo-code: + // iterate for output len : i = 0 to len + // get the input elem using input mapping index = M* i + // store in y + // replace this op with the output_mem // DEBUG_PRINT_NO_ARGS() ; - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); - //For loop - int64_t lb = 0 ; + // For loop + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv = forOp1.getInductionVar(); - rewriter.setInsertionPointToStart(forOp1.getBody()); DownsamplingOpAdaptor downsamplingAdaptor(operands); - - //For affine expression: #map1 = affine_map<(%arg0)[2ndOperand] : (%arg0 * 2ndOperand) + + // For affine expression: #map1 = affine_map<(%arg0)[2ndOperand] : (%arg0 * + // 2ndOperand) AffineExpr d0, s0; bindDims(rewriter.getContext(), d0); bindSymbols(rewriter.getContext(), s0); - // AffineExpr ExprForDownSampling = rewriter.getAffineDimExpr(0) * rewriter.getAffineSymbolExpr(0); + // AffineExpr ExprForDownSampling = rewriter.getAffineDimExpr(0) * + // rewriter.getAffineSymbolExpr(0); AffineExpr ExprForDownSampling = d0 * s0; - // Value constant3 = rewriter.create(loc, rewriter.getI64Type(), rewriter.getIntegerAttr(rewriter.getIntegerType(64), 3)); - Value constant3 = rewriter.create(loc, 3); //working + // Value constant3 = rewriter.create(loc, + // rewriter.getI64Type(), + // rewriter.getIntegerAttr(rewriter.getIntegerType(64), 3)); + Value constant3 = + rewriter.create(loc, 3); // working constant3.dump(); int64_t SecondValueInt = 1; Value downsampling2ndArg = op->getOperand(1); - dsp::ConstantOp constantOp2ndArg = downsampling2ndArg.getDefiningOp(); - DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue();; + dsp::ConstantOp constantOp2ndArg = + downsampling2ndArg.getDefiningOp(); + DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue(); + ; auto elements = constantRhsValue.getValues(); float SecondValue = elements[0].getValueAsDouble(); - SecondValueInt = (int64_t) SecondValue; + SecondValueInt = (int64_t)SecondValue; - // Value downSamplingRateAsIndex = rewriter.create(loc, rewriter.getIndexType(),DownsamplingRate); - Value constantSamplingRateIndx = rewriter.create(loc, SecondValueInt); + // Value downSamplingRateAsIndex = rewriter.create(loc, + // rewriter.getIndexType(),DownsamplingRate); + Value constantSamplingRateIndx = + rewriter.create(loc, SecondValueInt); constantSamplingRateIndx.dump(); - + AffineMap addMapForDownSampling = AffineMap::get(1, 1, ExprForDownSampling); - // AffineMap addMapForDownSampling = AffineMap::get(1, 1, ValueRange{d0,s0 }); - // AffineMap addMapForDownSampling = AffineMap::get(1, 1, ExprForDownSampling, rewriter.getContext()); - // AffineMap addMapForDownSampling = AffineMap::get(1, 0, { d0}); //Working + // AffineMap addMapForDownSampling = AffineMap::get(1, 1, ValueRange{d0,s0 + // }); AffineMap addMapForDownSampling = AffineMap::get(1, 1, + // ExprForDownSampling, rewriter.getContext()); AffineMap + // addMapForDownSampling = AffineMap::get(1, 0, { d0}); //Working // DEBUG_PRINT_NO_ARGS() ; - Value elem2 = rewriter.create(loc, downsamplingAdaptor.getLhs(), addMapForDownSampling, - ValueRange{iv,constantSamplingRateIndx}); + Value elem2 = rewriter.create( + loc, downsamplingAdaptor.getLhs(), addMapForDownSampling, + ValueRange{iv, constantSamplingRateIndx}); elem2.dump(); - //store the result + // store the result rewriter.create(loc, elem2, alloc, iv); rewriter.setInsertionPointAfter(forOp1); - //debug - // forOp1->dump(); - // %2ndOperand = arith.const 3 : f64 - // affine.for %arg0 = 0 to 10 { - // #map1 = affine_map<(%arg0)[2ndOperand] : (%arg0 * 2ndOperand) - // %elem1 = affine.load input[#map1] <-- affine apply - // affine.store %elem1, out[%arg0] - // } + // debug + // forOp1->dump(); + // %2ndOperand = arith.const 3 : f64 + // affine.for %arg0 = 0 to 10 { + // #map1 = affine_map<(%arg0)[2ndOperand] : (%arg0 * 2ndOperand) + // %elem1 = affine.load input[#map1] <-- affine apply + // affine.store %elem1, out[%arg0] + // } rewriter.replaceOp(op, alloc); - + return success(); } }; @@ -5912,84 +6262,90 @@ struct DownSamplingOpLowering : public ConversionPattern { struct SlidingWindowAvgOpLowering : public ConversionPattern { SlidingWindowAvgOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::SlidingWindowAvgOp::getOperationName(), 1, ctx) {} + : ConversionPattern(dsp::SlidingWindowAvgOp::getOperationName(), 1, ctx) { + } LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { auto loc = op->getLoc(); - - //Pseudo-code: - //iterate for len = len - 2 - //get 3 elements - //get the sum - //get the avg = sum / 3 - // store the result to output_mem - // replace this op with the output_mem + + // Pseudo-code: + // iterate for len = len - 2 + // get 3 elements + // get the sum + // get the avg = sum / 3 + // store the result to output_mem + // replace this op with the output_mem // DEBUG_PRINT_NO_ARGS() ; - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); SmallVector steps(tensorType.getRank(), /*Value=*/1); - Value constant3 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3)); - //For loop - int64_t lb = 0 ; + Value constant3 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3)); + // For loop + int64_t lb = 0; int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); auto iv = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); SlidingWindowAvgOpAdaptor slidingWinAvgAdaptor(operands); - - Value elem1 = rewriter.create(loc, slidingWinAvgAdaptor.getInput(), iv); - //affine-maps for elem2 and elem3 - AffineExpr ExprForElem2 = rewriter.getAffineDimExpr(0) + rewriter.getAffineConstantExpr(1); - AffineExpr ExprForElem3 = rewriter.getAffineDimExpr(0) + rewriter.getAffineConstantExpr(2); + Value elem1 = + rewriter.create(loc, slidingWinAvgAdaptor.getInput(), iv); + + // affine-maps for elem2 and elem3 + AffineExpr ExprForElem2 = + rewriter.getAffineDimExpr(0) + rewriter.getAffineConstantExpr(1); + AffineExpr ExprForElem3 = + rewriter.getAffineDimExpr(0) + rewriter.getAffineConstantExpr(2); AffineMap addMapForElem2 = AffineMap::get(1, 0, ExprForElem2); AffineMap addMapForElem3 = AffineMap::get(1, 0, ExprForElem3); - Value elem2 = rewriter.create(loc, slidingWinAvgAdaptor.getInput(), addMapForElem2, - ValueRange{iv}); - Value elem3 = rewriter.create(loc, slidingWinAvgAdaptor.getInput(), addMapForElem3, - ValueRange{iv}); + Value elem2 = rewriter.create( + loc, slidingWinAvgAdaptor.getInput(), addMapForElem2, ValueRange{iv}); + Value elem3 = rewriter.create( + loc, slidingWinAvgAdaptor.getInput(), addMapForElem3, ValueRange{iv}); - Value sum1 = rewriter.create(loc, elem1 , elem2); - Value sum2 = rewriter.create(loc, sum1 , elem3); + Value sum1 = rewriter.create(loc, elem1, elem2); + Value sum2 = rewriter.create(loc, sum1, elem3); Value avg = rewriter.create(loc, sum2, constant3); - //store the result + // store the result rewriter.create(loc, avg, alloc, iv); rewriter.setInsertionPointAfter(forOp1); - //debug - // forOp1->dump(); - // %cont3 = arith.const 3 : f64 - // affine.for %arg0 = 0 to 8 { - // %elem1 = affine.load input[%arg0] - // #map1 = affine_map<(%arg0)[] : (%arg0 + 1) - // #map2 = affine_map<(%arg0)[] : (%arg0 + 2) - // %elem2 = affine.load input[#map1] <-- affine apply - // %elem3 = affine.load input[#map2] - - // %sum1 = arith.addf %elem1 , %elem2 - // %sum2 = arith.addf %sum1, %elem3 - // %res = arith.divf %sum2 , - // affine.store %sum2, out[%arg0] - // } + // debug + // forOp1->dump(); + // %cont3 = arith.const 3 : f64 + // affine.for %arg0 = 0 to 8 { + // %elem1 = affine.load input[%arg0] + // #map1 = affine_map<(%arg0)[] : (%arg0 + 1) + // #map2 = affine_map<(%arg0)[] : (%arg0 + 2) + // %elem2 = affine.load input[#map1] <-- affine apply + // %elem3 = affine.load input[#map2] + + // %sum1 = arith.addf %elem1 , %elem2 + // %sum2 = arith.addf %sum1, %elem3 + // %res = arith.divf %sum2 , + // affine.store %sum2, out[%arg0] + // } rewriter.replaceOp(op, alloc); - + return success(); } }; @@ -5997,146 +6353,157 @@ struct SlidingWindowAvgOpLowering : public ConversionPattern { //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: FIRFilterResponse operations //===----------------------------------------------------------------------===// -struct FIRFilterResponseOpLowering: public ConversionPattern { - FIRFilterResponseOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::FIRFilterResponseOp::getOperationName(), 1 , ctx) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - //dsp.FIRFilterResponseOp has 2 operands -- both of type tensor f64 - - //Get the location of FIRFilterResponseOp - auto loc = op->getLoc(); - - //Pseudo-Code - // y[n] = sum( h[k] * x[n-k]) k = 0 to lenOfh - - //Range for each element of the output tensor -- i = %arg0 - // Create a tempValue = 0 - // Range for each of the elements of filter len -- k = %arg1 - // check for the condition that %arg0 - %arg1 >= 0 && < inputLen - // get elem1 = filter[k] , elem2 = x[i-k] - // use affine-map expression for calculating i-k - // tempValue = tempValue + elem1 * elem2 - // y[i] = tempValue - - lowerOpToLoopsFIR(op, operands, rewriter, - [loc, op ] (OpBuilder &builder, ValueRange memRefOperands, - ValueRange loopIvs) { - // ValueRange loopIvs) { - - // Generate an adaptor for the remapped operands of the - // BinaryOp. This allows for using the nice named accessors - // that are generated by the ODS. - dsp::FIRFilterResponseOpAdaptor firFilterAdaptor(memRefOperands); +struct FIRFilterResponseOpLowering : public ConversionPattern { + FIRFilterResponseOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::FIRFilterResponseOp::getOperationName(), 1, + ctx) {} - // Generate loads for the element of 'lhs' and 'rhs' at the - // inner loop. - // auto lhsTensor = delayAdaptor.getLhs(); - auto lhsTensor = builder.create( - loc, firFilterAdaptor.getLhs(), loopIvs); + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + // dsp.FIRFilterResponseOp has 2 operands -- both of type tensor f64 + + // Get the location of FIRFilterResponseOp + auto loc = op->getLoc(); - // auto rhsScalar = op->getOperand(1); - auto rhsScalar = builder.create( - loc, firFilterAdaptor.getRhs(), loopIvs); + // Pseudo-Code + // y[n] = sum( h[k] * x[n-k]) k = 0 to lenOfh + + // Range for each element of the output tensor -- i = %arg0 + // Create a tempValue = 0 + // Range for each of the elements of filter len -- k = %arg1 + // check for the condition that %arg0 - %arg1 >= 0 && < inputLen + // get elem1 = filter[k] , elem2 = x[i-k] + // use affine-map expression for calculating i-k + // tempValue = tempValue + elem1 * elem2 + // y[i] = tempValue + + lowerOpToLoopsFIR( + op, operands, rewriter, + [loc, op](OpBuilder &builder, ValueRange memRefOperands, + ValueRange loopIvs) { + // ValueRange loopIvs) { - auto resultMulOp = builder.create(loc, lhsTensor, - rhsScalar); + // Generate an adaptor for the remapped operands of the + // BinaryOp. This allows for using the nice named accessors + // that are generated by the ODS. + dsp::FIRFilterResponseOpAdaptor firFilterAdaptor(memRefOperands); - return resultMulOp; + // Generate loads for the element of 'lhs' and 'rhs' at the + // inner loop. + // auto lhsTensor = delayAdaptor.getLhs(); + auto lhsTensor = builder.create( + loc, firFilterAdaptor.getLhs(), loopIvs); - }); + // auto rhsScalar = op->getOperand(1); + auto rhsScalar = builder.create( + loc, firFilterAdaptor.getRhs(), loopIvs); - return success(); - } + auto resultMulOp = + builder.create(loc, lhsTensor, rhsScalar); + return resultMulOp; + }); + return success(); + } }; //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: Delay operations //===----------------------------------------------------------------------===// -struct DelayOpLowering: public ConversionPattern { - DelayOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::DelayOp::getOperationName(), 1 , ctx) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - //dsp.DelayOp has 2 operands -- both of type tensor f64 - - //Get the location of delayop - auto loc = op->getLoc(); - - //Pseudo-code - //2 affine loops -- - //first from 0 to delay_2ndArg - // here, inside AffineNest - // create affine:load from the arith.const operation with value 0 - // use affine:store to store at result_op at indx - // - //2nd from delay_2ndArg to lengthOfOperand0 of delayOp - // here, inside AffineNest - // create affine:load from input memref & indx = indx - delay_2ndArg - // create affine:store at result_op indx - - //output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation +struct DelayOpLowering : public ConversionPattern { + DelayOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::DelayOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + // dsp.DelayOp has 2 operands -- both of type tensor f64 + + // Get the location of delayop + auto loc = op->getLoc(); + + // Pseudo-code + // 2 affine loops -- + // first from 0 to delay_2ndArg + // here, inside AffineNest + // create affine:load from the arith.const operation with value 0 + // use affine:store to store at result_op at indx + // + // 2nd from delay_2ndArg to lengthOfOperand0 of delayOp + // here, inside AffineNest + // create affine:load from input memref & indx = indx - + // delay_2ndArg create affine:store at result_op indx + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); SmallVector steps(tensorType.getRank(), /*Value=*/1); - //For loop + // For loop int64_t ub = tensorType.getShape()[0]; - //Get 2nd Arg + // Get 2nd Arg DelayOpAdaptor delayOpAdaptor(operands); - Value constant0 = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); DEBUG_PRINT_NO_ARGS(); // Creating SSA values for the lower bound and upper bound - Value lowerBound = rewriter.create(loc, rewriter.getIndexType(), rewriter.getIntegerAttr(rewriter.getIndexType(), 0)); + Value lowerBound = rewriter.create( + loc, rewriter.getIndexType(), + rewriter.getIntegerAttr(rewriter.getIndexType(), 0)); // Cast the f64 value directly to the index type - Value inputUnit = rewriter.create(loc, delayOpAdaptor.getRhs(), ValueRange{} ); - Value i64UpperBound = rewriter.create(loc, rewriter.getI64Type(), inputUnit); + Value inputUnit = rewriter.create( + loc, delayOpAdaptor.getRhs(), ValueRange{}); + Value i64UpperBound = + rewriter.create(loc, rewriter.getI64Type(), inputUnit); // Cast the i64 value to index type - Value delay2ndArg = rewriter.create(loc, rewriter.getIndexType(), i64UpperBound); - // Value inputLen = rewriter.create(loc, rewriter.getIndexType(), rewriter.getIntegerAttr(rewriter.getIndexType(), ub)); + Value delay2ndArg = rewriter.create( + loc, rewriter.getIndexType(), i64UpperBound); + // Value inputLen = rewriter.create(loc, + // rewriter.getIndexType(), rewriter.getIntegerAttr(rewriter.getIndexType(), + // ub)); DEBUG_PRINT_WITH_ARGS("print delay2ndArg.dump() for debugging"); - + DEBUG_PRINT_NO_ARGS(); // Create an empty affine map list // SmallVector lbMaps, ubMaps; // Create identity affine maps for bounds - // AffineMap lbMap = AffineMap::get(/*dimCount=*/0, /*symbolCount=*/0, rewriter.getContext()); - // AffineMap ubMap = AffineMap::get(/*dimCount=*/0, /*symbolCount=*/0, rewriter.getContext()); + // AffineMap lbMap = AffineMap::get(/*dimCount=*/0, /*symbolCount=*/0, + // rewriter.getContext()); AffineMap ubMap = AffineMap::get(/*dimCount=*/0, + // /*symbolCount=*/0, rewriter.getContext()); // Create an AffineForOp with SSA values for the bounds Value step1 = rewriter.create(loc, 1); - scf::ForOp forOp1 = rewriter.create(loc, lowerBound, delay2ndArg, step1); - //Affine loop with non-int loop indices - // affine::AffineForOp forOp1 = rewriter.create(loc, lowerBound, lbMap, inputLen, ubMap, 1); + scf::ForOp forOp1 = + rewriter.create(loc, lowerBound, delay2ndArg, step1); + // Affine loop with non-int loop indices + // affine::AffineForOp forOp1 = rewriter.create(loc, + // lowerBound, lbMap, inputLen, ubMap, 1); DEBUG_PRINT_NO_ARGS(); - + auto iv = forOp1.getInductionVar(); rewriter.setInsertionPointToStart(forOp1.getBody()); - //store the result - // rewriter.create(loc, constant0, alloc, iv); + // store the result + // rewriter.create(loc, constant0, alloc, iv); rewriter.create(loc, constant0, alloc, iv); rewriter.setInsertionPointAfter(forOp1); // Create the constants for lb2, step1, and calculate ub2 Value lb2 = rewriter.create(loc, 0); - Value lenOfInput = rewriter.create(loc, /*length of input*/ub); // Replace with the actual length + Value lenOfInput = rewriter.create( + loc, /*length of input*/ ub); // Replace with the actual length Value ub2 = rewriter.create(loc, lenOfInput, delay2ndArg); Value step2 = rewriter.create(loc, 1); @@ -6148,7 +6515,8 @@ struct DelayOpLowering: public ConversionPattern { rewriter.setInsertionPointToStart(forOp2.getBody()); // Load value from allocIP[iv2] - Value loadedVal = rewriter.create(loc, delayOpAdaptor.getLhs(), iv2); + Value loadedVal = + rewriter.create(loc, delayOpAdaptor.getLhs(), iv2); // Calculate the index iv2 + delaySecondArg Value newIndex = rewriter.create(loc, iv2, delay2ndArg); @@ -6157,135 +6525,340 @@ struct DelayOpLowering: public ConversionPattern { rewriter.create(loc, loadedVal, alloc, newIndex); rewriter.setInsertionPointAfter(forOp2); DEBUG_PRINT_NO_ARGS(); - //For 2nd loop -- - //loop from 0 to lenOfInput - 2ndArg - // load from index - // store at index + 2ndArg + // For 2nd loop -- + // loop from 0 to lenOfInput - 2ndArg + // load from index + // store at index + 2ndArg // forOp1.dump(); - //Expected MLIR-Affine - // %0 = affine.load %alloc_0[] : memref - // %1 = arith.fptosi %0 : f64 to i64 - // %2 = arith.index_cast %1 : i64 to index - // %c1_15 = arith.constant 1 : index - // scf.for %arg0 = %c0_14 to %2 step %c1_15 { - // memref.store %cst_13, %alloc[%arg0] : memref<10xf64> - // } - // %c0_16 = arith.constant 0 : index - // %c10 = arith.constant 10 : index - // %3 = arith.subi %c10, %2 : index - // %c1_17 = arith.constant 1 : index - // scf.for %arg0 = %c0_16 to %3 step %c1_17 { - // %4 = memref.load %alloc_1[%arg0] : memref<10xf64> - // %5 = arith.addi %arg0, %2 : index - // memref.store %4, %alloc[%5] : memref<10xf64> - // } - - + // Expected MLIR-Affine + // %0 = affine.load %alloc_0[] : memref + // %1 = arith.fptosi %0 : f64 to i64 + // %2 = arith.index_cast %1 : i64 to index + // %c1_15 = arith.constant 1 : index + // scf.for %arg0 = %c0_14 to %2 step %c1_15 { + // memref.store %cst_13, %alloc[%arg0] : memref<10xf64> + // } + // %c0_16 = arith.constant 0 : index + // %c10 = arith.constant 10 : index + // %3 = arith.subi %c10, %2 : index + // %c1_17 = arith.constant 1 : index + // scf.for %arg0 = %c0_16 to %3 step %c1_17 { + // %4 = memref.load %alloc_1[%arg0] : memref<10xf64> + // %5 = arith.addi %arg0, %2 : index + // memref.store %4, %alloc[%5] : memref<10xf64> + // } rewriter.replaceOp(op, alloc); DEBUG_PRINT_NO_ARGS(); return success(); - } - - + } }; //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: Gain operations //===----------------------------------------------------------------------===// -struct GainOpLowering: public ConversionPattern { - GainOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::GainOp::getOperationName(), 1 , ctx) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - //dsp.GainOp has 2 operands -- both of type tensor f64 , 2ndOperand should have only 1 element - - //Get the location of GainOp - auto loc = op->getLoc(); - - - //Pseudo-code: - // y[i] = y[i] * gain for 0<=i((*op->result_type_begin())); - - //allocation & deallocation for the result of this operation +struct GainOpLowering : public ConversionPattern { + GainOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::GainOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + // dsp.GainOp has 2 operands -- both of type tensor f64 , 2ndOperand should + // have only 1 element + + // Get the location of GainOp + auto loc = op->getLoc(); + + // Pseudo-code: + // y[i] = y[i] * gain for 0<=i((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - //construct affine loops for the input - SmallVector lowerBounds(tensorType.getRank(), /*Value*/0); - SmallVector steps(tensorType.getRank(), /*Value=*/1); + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); GainOpAdaptor gainOpOpAdaptor(operands); // Value GetValueAtIndx2ndArg = op->getOperand(1); - // dsp::ConstantOp constantOp2ndArg = GetValueAtIndx2ndArg.getDefiningOp(); - // DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue();; - // auto elements = constantRhsValue.getValues(); - // float gain = elements[0].getValueAsDouble(); + // dsp::ConstantOp constantOp2ndArg = + // GetValueAtIndx2ndArg.getDefiningOp(); DenseElementsAttr + // constantRhsValue = constantOp2ndArg.getValue();; auto elements = + // constantRhsValue.getValues(); float gain = + // elements[0].getValueAsDouble(); // Value gain = gainOpOpAdaptor.getRhs(); - + DEBUG_PRINT_NO_ARGS(); - //first from 1 <= i < N - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0]; + // first from 1 <= i < N + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; int64_t step = 1; DEBUG_PRINT_NO_ARGS(); - - //loop from 0 <= i < N + // loop from 0 <= i < N - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - Value getLhs = rewriter.create(loc, gainOpOpAdaptor.getLhs() , ivY); - Value getRhs = rewriter.create(loc, gainOpOpAdaptor.getRhs() , ValueRange{}); - Value mulProd = rewriter.create(loc, getLhs, getRhs ); - rewriter.create(loc, mulProd, alloc, ValueRange{ivY}); + Value getLhs = + rewriter.create(loc, gainOpOpAdaptor.getLhs(), ivY); + Value getRhs = rewriter.create(loc, gainOpOpAdaptor.getRhs(), + ValueRange{}); + Value mulProd = rewriter.create(loc, getLhs, getRhs); + rewriter.create(loc, mulProd, alloc, ValueRange{ivY}); DEBUG_PRINT_NO_ARGS(); rewriter.setInsertionPointAfter(forOpY); + // debug + // forOpX->dump(); + // forOpY->dump(); + + // %cst = arith.constant 6.2831853071800001 : f64 + // %cst_0 = arith.constant 4.600000e-01 : f64 + // %cst_1 = arith.constant 5.400000e-01 : f64 + // %cst_2 = arith.constant 4.000000e+00 : f64 + // %alloc = memref.alloc() : memref<4xf64> + // %alloc_3 = memref.alloc() : memref + // affine.store %cst_2, %alloc_3[] : memref + // affine.for %arg0 = 0 to 4 { + // %0 = arith.index_castui %arg0 : index to i32 + // %1 = arith.uitofp %0 : i32 to f64 + // %2 = arith.mulf %1, %cst : f64 + // %3 = arith.divf %2, %cst_2 : f64 + // %4 = math.cos %3 : f64 + // %5 = arith.mulf %4, %cst_0 : f64 + // %6 = arith.subf %cst_1, %5 : f64 + // affine.store %6, %alloc[%arg0] : memref<4xf64> + // } - //debug - // forOpX->dump(); - // forOpY->dump(); + // } + // } + rewriter.replaceOp(op, alloc); + + return success(); + } +}; + +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: BitwiseAndOp operations +//===----------------------------------------------------------------------===// + +struct BitwiseAndOpLowering : public ConversionPattern { + BitwiseAndOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::BitwiseAndOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + // dsp.bitwiseandop has 2 operands -- both of type tensor f64 , of the same + // size + + // Get the location of BitwiseAndOp + auto loc = op->getLoc(); + + // Pseudo-code: + // y[i] = bitwiseand(lhs[i], rhs[i]) for 0<=i((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation + auto memRefType = convertTensorToMemRef(tensorType); + auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); + + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); + BitwiseAndOpAdaptor bitwiseandOpAdaptor(operands); + + DEBUG_PRINT_NO_ARGS(); + + // first from 0 <= i < N + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + DEBUG_PRINT_NO_ARGS(); + // loop from 0 <= i < N + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); + auto ivY = forOpY.getInductionVar(); + rewriter.setInsertionPointToStart(forOpY.getBody()); + + Value getLhs = + rewriter.create(loc, bitwiseandOpAdaptor.getLhs(), ivY); + Value getRhs = + rewriter.create(loc, bitwiseandOpAdaptor.getRhs(), ivY); + Value lhsInt = + rewriter.create(loc, rewriter.getI64Type(), getLhs); + Value rhsInt = + rewriter.create(loc, rewriter.getI64Type(), getRhs); + Value andiResult = rewriter.create(loc, lhsInt, rhsInt); + Value resultFp = rewriter.create( + loc, rewriter.getF64Type(), andiResult); + + rewriter.create(loc, resultFp, alloc, ValueRange{ivY}); + rewriter.setInsertionPointAfter(forOpY); + + // debug + forOpY->dump(); - // %cst = arith.constant 6.2831853071800001 : f64 - // %cst_0 = arith.constant 4.600000e-01 : f64 - // %cst_1 = arith.constant 5.400000e-01 : f64 - // %cst_2 = arith.constant 4.000000e+00 : f64 - // %alloc = memref.alloc() : memref<4xf64> - // %alloc_3 = memref.alloc() : memref - // affine.store %cst_2, %alloc_3[] : memref - // affine.for %arg0 = 0 to 4 { - // %0 = arith.index_castui %arg0 : index to i32 - // %1 = arith.uitofp %0 : i32 to f64 - // %2 = arith.mulf %1, %cst : f64 - // %3 = arith.divf %2, %cst_2 : f64 - // %4 = math.cos %3 : f64 - // %5 = arith.mulf %4, %cst_0 : f64 - // %6 = arith.subf %cst_1, %5 : f64 - // affine.store %6, %alloc[%arg0] : memref<4xf64> - // } - - - // } - // } rewriter.replaceOp(op, alloc); - + return success(); - } + }; +}; + +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: BitwiseAndOp operations +//===----------------------------------------------------------------------===// + +struct zeroCrossCountOpLowering : public ConversionPattern { + zeroCrossCountOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::zeroCrossCountOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + // dsp.zerocrosscount has 1 operand -- of type tensor f64 + + // Get the location of zeroCrossCountOp + auto loc = op->getLoc(); + + // Pseudo-code is based on the C++ implementation here: + // https://toto-share.com/2011/05/cc-zero-crossing-code/ + // for 1<=i((*op->result_type_begin())); + Type integerType = rewriter.getI64Type(); + + // allocation & deallocation for the result of this operation + auto memRefType = convertTensorToMemRef(tensorType); + // Force the result to be a tensor of size 1 + auto alloc = insertAllocAndDealloc( + MemRefType::get(ArrayRef(1), tensorType.getElementType()), loc, + rewriter); + zeroCrossCountOpAdaptor zeroCrossCountOpAdaptor(operands); + DEBUG_PRINT_NO_ARGS(); + + // Define constants + Value constant0 = rewriter.create( + loc, rewriter.getI64Type(), + rewriter.getIntegerAttr(rewriter.getI64Type(), 0)); + Value constant1 = rewriter.create( + loc, rewriter.getI64Type(), + rewriter.getIntegerAttr(rewriter.getI64Type(), 1)); + Value Indx0 = rewriter.create(loc, 0); + + // Define bounds + Value lb = rewriter.create( + loc, rewriter.getIndexType(), + rewriter.getIntegerAttr(rewriter.getIndexType(), 1)); + Value ub = rewriter.create( + loc, rewriter.getIndexType(), + rewriter.getIntegerAttr(rewriter.getIndexType(), + tensorType.getShape()[0])); + Value step = rewriter.create(loc, 1); + + // Set up for loop + auto forOpY = + rewriter.create(loc, lb, ub, step, ValueRange{constant0}); + auto ivY = forOpY.getInductionVar(); + rewriter.setInsertionPointToStart(forOpY.getBody()); + auto countArg = forOpY.getRegionIterArgs()[0]; + + // Get the current and previous elements + Value ivYPrev = rewriter.create(loc, ivY, step); + Value getLhsPrev = rewriter.create( + loc, zeroCrossCountOpAdaptor.getLhs(), ivYPrev); + Value getLhs = rewriter.create( + loc, zeroCrossCountOpAdaptor.getLhs(), ivY); + + // Convert from float to integer + Value lhsPrevInt = rewriter.create( + loc, rewriter.getI64Type(), getLhsPrev); + Value lhsInt = + rewriter.create(loc, rewriter.getI64Type(), getLhs); + + // Check whether the elements are less than zero + Value signLhsPrev = rewriter.create( + loc, arith::CmpIPredicate::slt, lhsPrevInt, constant0); + Value signLhs = rewriter.create( + loc, arith::CmpIPredicate::slt, lhsInt, constant0); + Value equal = rewriter.create(loc, arith::CmpIPredicate::eq, + signLhsPrev, signLhs); + + // If the signs aren't the same, increment the zero cross counter + auto ifOp = + rewriter.create(loc, TypeRange{integerType}, equal, true); + + // If block + rewriter.setInsertionPointToStart(ifOp.thenBlock()); + rewriter.create(loc, ValueRange{countArg}); + // Else block + rewriter.setInsertionPointToStart(ifOp.elseBlock()); + auto countPlusOne = + rewriter.create(loc, countArg, constant1); + rewriter.create(loc, ValueRange{countPlusOne}); + + rewriter.setInsertionPointAfter(ifOp); + auto countRes = ifOp.getResults()[0]; + rewriter.create(loc, ValueRange{countRes}); + + rewriter.setInsertionPointAfter(forOpY); + + // debug + // forOpY->dump(); + // %15 = "scf.for"(%12, %13, %14, %9) ({ + // ^bb0(%arg0: index, %arg1: i64): + // %17 = "arith.subi"(%arg0, %14) <{overflowFlags = + // #arith.overflow}> + // : (index, index) -> index %18 = "memref.load"(%1, %17) <{nontemporal = + // false}> : (memref<3xf64>, index) -> f64 %19 = "memref.load"(%1, %arg0) + // <{nontemporal = false}> : (memref<3xf64>, index) -> f64 %20 = + // "arith.fptosi"(%18) : (f64) -> i64 %21 = "arith.fptosi"(%19) : (f64) -> + // i64 + // %22 = "arith.cmpi"(%20, %9) <{predicate = 2 : i64}> : (i64, i64) -> + // i1 %23 = "arith.cmpi"(%21, %9) <{predicate = 2 : i64}> : (i64, i64) + // -> i1 %24 = "arith.cmpi"(%22, %23) <{predicate = 0 : i64}> : (i1, i1) + // -> i1 %25 = "scf.if"(%24) ({ + // "scf.yield"(%arg1) : (i64) -> () + // }, { + // %26 = "arith.addi"(%arg1, %10) <{overflowFlags = + // #arith.overflow}> : (i64, i64) -> i64 "scf.yield"(%26) : (i64) -> + // () + // }) : (i1) -> i64 + // "scf.yield"(%25) : (i64) -> () + // }) : (index, index, index, i64) -> i64 + + auto finalCountArg = forOpY.getResults()[0]; + Value finalCountArgFloat = rewriter.create( + loc, rewriter.getF64Type(), finalCountArg); + + rewriter.create(loc, finalCountArgFloat, alloc, Indx0); + rewriter.replaceOp(op, alloc); + + return success(); + }; }; + //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: Binary operations //===----------------------------------------------------------------------===// @@ -6323,40 +6896,39 @@ struct BinaryOpLowering : public ConversionPattern { } }; +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: Unary operations +//===----------------------------------------------------------------------===// + +template +struct UnaryOpLowering : public ConversionPattern { + UnaryOpLowering(MLIRContext *ctx) + : ConversionPattern(UnaryOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + lowerOpToLoops(op, operands, rewriter, + [loc](OpBuilder &builder, ValueRange memRefOperands, + ValueRange loopIvs) { + // Generate an adaptor for the remapped operands of the + // UnaryOp. This allows for using the nice named accessors + // that are generated by the ODS. + typename UnaryOp::Adaptor unaryAdaptor(memRefOperands); + + // Generate loads for the element of 'lhs' and 'rhs' at the + // inner loop. + auto loadedInput = builder.create( + loc, unaryAdaptor.getInput(), loopIvs); - //===----------------------------------------------------------------------===// - // ToyToAffine RewritePatterns: Unary operations - //===----------------------------------------------------------------------===// - - template - struct UnaryOpLowering : public ConversionPattern { - UnaryOpLowering(MLIRContext *ctx) - : ConversionPattern(UnaryOp::getOperationName(), 1, ctx) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - auto loc = op->getLoc(); - lowerOpToLoops(op, operands, rewriter, - [loc](OpBuilder &builder, ValueRange memRefOperands, - ValueRange loopIvs) { - // Generate an adaptor for the remapped operands of the - // UnaryOp. This allows for using the nice named accessors - // that are generated by the ODS. - typename UnaryOp::Adaptor unaryAdaptor(memRefOperands); - - // Generate loads for the element of 'lhs' and 'rhs' at the - // inner loop. - auto loadedInput = builder.create( - loc, unaryAdaptor.getInput(), loopIvs); - - // Create the unary operation performed on the loaded - // values. - return builder.create(loc, loadedInput); - }); - return success(); - } - }; + // Create the unary operation performed on the loaded + // values. + return builder.create(loc, loadedInput); + }); + return success(); + } +}; using AddOpLowering = BinaryOpLowering; using ModuloOpLowering = BinaryOpLowering; @@ -6478,7 +7050,7 @@ struct PrintOpLowering : public OpConversionPattern { // We don't lower "dsp.print" in this pass, but we need to update its // operands. rewriter.modifyOpInPlace(op, - [&] { op->setOperands(adaptor.getOperands()); }); + [&] { op->setOperands(adaptor.getOperands()); }); return success(); } }; @@ -6549,9 +7121,9 @@ struct ToyToAffineLoweringPass MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ToyToAffineLoweringPass) void getDependentDialects(DialectRegistry ®istry) const override { - registry.insert(); + registry + .insert(); } void runOnOperation() final; }; @@ -6585,23 +7157,25 @@ void ToyToAffineLoweringPass::runOnOperation() { // Now that the conversion target has been defined, we just need to provide // the set of patterns that will lower the Toy operations. RewritePatternSet patterns(&getContext()); - patterns.add( - &getContext()); + patterns.add< + AddOpLowering, ModuloOpLowering, ConstantOpLowering, FuncOpLowering, MulOpLowering, + PrintOpLowering, ReturnOpLowering, TransposeOpLowering, DelayOpLowering, + GainOpLowering, SubOpLowering, FIRFilterResponseOpLowering, + SlidingWindowAvgOpLowering, DownSamplingOpLowering, UpSamplingOpLowering, + LowPassFilter1stOrderOpLowering, HighPassFilterOpLowering, + FFT1DOpLowering, IFFT1DOpLowering, HammingWindowOpLowering, DCTOpLowering, + filterOpLowering, DivOpLowering, BitwiseAndOpLowering, + zeroCrossCountOpLowering, SumOpLowering, SinOpLowering, CosOpLowering, + SquareOpLowering, FFT1DRealOpLowering, FFT1DImgOpLowering, SincOpLowering, + GetElemAtIndxOpLowering, SetElemAtIndxOpLowering, + LowPassFIRFilterOpLowering, HighPassFIRFilterOpLowering, + GetRangeOfVectorOpLowering, FIRFilterHammingOptimizedOpLowering, + HighPassFIRHammingOptimizedOpLowering, LMSFilterOpLowering, + ThresholdOpLowering, QuantizationOpLowering, LMSFilterResponseOpLowering, + RunLenEncodingOpLowering, FIRFilterResSymmOptimizedOpLowering, + LengthOpLowering, ReverseInputOpLowering, PaddingOpLowering, + FIRFilterYSymmOptimizedOpLowering, FFT1DRealSymmOpLowering, + FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering>(&getContext()); // With the target and rewrite patterns defined, we can now attempt the // conversion. The conversion will signal failure if any of our `illegal` diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index f62b2d13c0a4..2e9e5085d7d9 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -15,25 +15,24 @@ #include "toy/AST.h" #include "toy/Dialect.h" - -#include "mlir/IR/Block.h" -#include "mlir/IR/Diagnostics.h" -#include "mlir/IR/Value.h" -#include "mlir/Support/LogicalResult.h" #include "mlir/IR/Attributes.h" +#include "mlir/IR/Block.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Diagnostics.h" #include "mlir/IR/MLIRContext.h" +#include "mlir/IR/Value.h" #include "mlir/IR/Verifier.h" +#include "mlir/Support/LogicalResult.h" #include "toy/Lexer.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopedHashTable.h" -#include "llvm/Support/raw_ostream.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" +#include "llvm/Support/raw_ostream.h" #include #include #include @@ -332,6 +331,16 @@ class MLIRGenImpl { // Builtin calls have their custom operation, meaning this is a // straightforward emission. + + if (callee == "bitwiseand") { + if (call.getArgs().size() != 2) { + emitError(location, "MLIR codegen encountered an error: dsp.bitwiseand " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); + } + if (callee == "transpose") { if (call.getArgs().size() != 1) { emitError(location, "MLIR codegen encountered an error: dsp.transpose " @@ -342,32 +351,32 @@ class MLIRGenImpl { } // - if(callee == "delay"){ - if(call.getArgs().size() != 2){ + if (callee == "delay") { + if (call.getArgs().size() != 2) { emitError(location, "MLIR codegen encountered an error: dsp.delay " "accepts only 2 arguments"); return nullptr; } - return builder.create(location, operands[0] , operands[1]); + return builder.create(location, operands[0], operands[1]); } - if(callee == "gain"){ - if(call.getArgs().size() != 2){ + if (callee == "gain") { + if (call.getArgs().size() != 2) { emitError(location, "MLIR codegen encountered an error: dsp.gain " "accepts only 2 arguments"); return nullptr; } - return builder.create(location, operands[0] , operands[1]); + return builder.create(location, operands[0], operands[1]); } // Sub Op - if(callee == "sub"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.sub " - "accepts only 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1]); + if (callee == "sub") { + if (call.getArgs().size() != 2) { + emitError(location, "MLIR codegen encountered an error: dsp.sub " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); } // Modulo Op @@ -413,56 +422,62 @@ class MLIRGenImpl { "accepts only 2 arguments"); return nullptr; } - return builder.create(location, operands[0] , operands[1]); + return builder.create(location, operands[0], + operands[1]); } - if(callee == "slidingWindowAvg"){ - if(call.getArgs().size() != 1){ - emitError(location, "MLIR codegen encountered an error: dsp.slidingWindowAvg " - "accepts only 1 arguments"); + if (callee == "slidingWindowAvg") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.slidingWindowAvg " + "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0] ); + return builder.create(location, operands[0]); } - if(callee == "downsampling"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.downsampling " - "accepts only 2 arguments"); + if (callee == "downsampling") { + if (call.getArgs().size() != 2) { + emitError(location, + "MLIR codegen encountered an error: dsp.downsampling " + "accepts only 2 arguments"); return nullptr; } - return builder.create(location, operands[0] , operands[1]); + return builder.create(location, operands[0], operands[1]); } - if(callee == "upsampling"){ - if(call.getArgs().size() != 2){ + if (callee == "upsampling") { + if (call.getArgs().size() != 2) { emitError(location, "MLIR codegen encountered an error: dsp.upsampling " "accepts only 2 arguments"); return nullptr; } - return builder.create(location, operands[0] , operands[1]); + return builder.create(location, operands[0], operands[1]); } - if(callee == "lowPassFilter"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.lowPassFilter " - "accepts only 2 arguments"); + if (callee == "lowPassFilter") { + if (call.getArgs().size() != 2) { + emitError(location, + "MLIR codegen encountered an error: dsp.lowPassFilter " + "accepts only 2 arguments"); return nullptr; } - return builder.create(location, operands[0] , operands[1]); + return builder.create(location, operands[0], + operands[1]); } - if(callee == "highPassFilter"){ - if(call.getArgs().size() != 1){ - emitError(location, "MLIR codegen encountered an error: dsp.highPassFilter " - "accepts only 1 arguments"); + if (callee == "highPassFilter") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.highPassFilter " + "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0] ); + return builder.create(location, operands[0]); } - if(callee == "fft1d"){ - if(call.getArgs().size() != 1){ + if (callee == "fft1d") { + if (call.getArgs().size() != 1) { emitError(location, "MLIR codegen encountered an error: dsp.fft1d " "accepts only 1 arguments"); return nullptr; @@ -470,26 +485,26 @@ class MLIRGenImpl { // return builder.create(location, operands[0] ); } - if(callee == "fft1dreal"){ - if(call.getArgs().size() != 1){ + if (callee == "fft1dreal") { + if (call.getArgs().size() != 1) { emitError(location, "MLIR codegen encountered an error: dsp.fft1dreal " "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0] ); + return builder.create(location, operands[0]); } - if(callee == "fft1dimg"){ - if(call.getArgs().size() != 1){ + if (callee == "fft1dimg") { + if (call.getArgs().size() != 1) { emitError(location, "MLIR codegen encountered an error: dsp.fft1dimg " "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0] ); + return builder.create(location, operands[0]); } - if(callee == "ifft1d"){ - if(call.getArgs().size() != 2){ + if (callee == "ifft1d") { + if (call.getArgs().size() != 2) { emitError(location, "MLIR codegen encountered an error: dsp.ifft1d " "accepts only 1 arguments"); return nullptr; @@ -497,263 +512,294 @@ class MLIRGenImpl { return builder.create(location, operands[0], operands[1]); } - if(callee == "hamming"){ - if(call.getArgs().size() != 1){ + if (callee == "hamming") { + if (call.getArgs().size() != 1) { emitError(location, "MLIR codegen encountered an error: dsp.hamming " "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0] ); + return builder.create(location, operands[0]); } - if(callee == "dct"){ - if(call.getArgs().size() != 1){ + if (callee == "dct") { + if (call.getArgs().size() != 1) { emitError(location, "MLIR codegen encountered an error: dsp.dct " "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0] ); + return builder.create(location, operands[0]); } - if(callee == "filter"){ - if(call.getArgs().size() != 3){ + if (callee == "filter") { + if (call.getArgs().size() != 3) { emitError(location, "MLIR codegen encountered an error: dsp.filter " "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0],operands[1], operands[2] ); + return builder.create(location, operands[0], operands[1], + operands[2]); } - if(callee == "div"){ - if(call.getArgs().size() != 2){ + if (callee == "div") { + if (call.getArgs().size() != 2) { emitError(location, "MLIR codegen encountered an error: dsp.div " "accepts only 2 arguments"); return nullptr; } - return builder.create(location, operands[0] , operands[1]); + return builder.create(location, operands[0], operands[1]); } - if(callee == "sum"){ - if(call.getArgs().size() != 1){ + if (callee == "sum") { + if (call.getArgs().size() != 1) { emitError(location, "MLIR codegen encountered an error: dsp.sum " "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0] ); + return builder.create(location, operands[0]); } - if(callee == "sin"){ - if(call.getArgs().size() != 1){ - emitError(location, "MLIR codegen encountered an error: dsp.sin " - "accepts only 1 arguments"); - return nullptr; - } - return builder.create(location, operands[0] ); - } + if (callee == "sin") { + if (call.getArgs().size() != 1) { + emitError(location, "MLIR codegen encountered an error: dsp.sin " + "accepts only 1 arguments"); + return nullptr; + } + return builder.create(location, operands[0]); + } - if(callee == "cos"){ - if(call.getArgs().size() != 1){ - emitError(location, "MLIR codegen encountered an error: dsp.cos " - "accepts only 1 arguments"); - return nullptr; - } - return builder.create(location, operands[0] ); - } + if (callee == "cos") { + if (call.getArgs().size() != 1) { + emitError(location, "MLIR codegen encountered an error: dsp.cos " + "accepts only 1 arguments"); + return nullptr; + } + return builder.create(location, operands[0]); + } - if(callee == "square"){ - if(call.getArgs().size() != 1){ + if (callee == "square") { + if (call.getArgs().size() != 1) { emitError(location, "MLIR codegen encountered an error: dsp.square " "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0] ); + return builder.create(location, operands[0]); } // Sinc Op - if(callee == "sinc"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.sinc " - "accepts only 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1]); + if (callee == "sinc") { + if (call.getArgs().size() != 2) { + emitError(location, "MLIR codegen encountered an error: dsp.sinc " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); } // Get Elem At Op - if(callee == "getElemAtIndx"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.getElemAtIndx " - "accepts only 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1]); + if (callee == "getElemAtIndx") { + if (call.getArgs().size() != 2) { + emitError(location, + "MLIR codegen encountered an error: dsp.getElemAtIndx " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], + operands[1]); } // Set Elem At Indx - if(callee == "setElemAtIndx"){ - if(call.getArgs().size() != 3){ - emitError(location, "MLIR codegen encountered an error: dsp.setElemAtIndx " - "accepts only 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1], operands[2]); + if (callee == "setElemAtIndx") { + if (call.getArgs().size() != 3) { + emitError(location, + "MLIR codegen encountered an error: dsp.setElemAtIndx " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1], + operands[2]); } // lowPassFilter Op - if(callee == "lowPassFIRFilter"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.lowPassFilter " - "accepts only 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1]); + if (callee == "lowPassFIRFilter") { + if (call.getArgs().size() != 2) { + emitError(location, + "MLIR codegen encountered an error: dsp.lowPassFilter " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], + operands[1]); } // highPassFilter Op - if(callee == "highPassFIRFilter"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.highPassFilter " - "accepts only 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1]); + if (callee == "highPassFIRFilter") { + if (call.getArgs().size() != 2) { + emitError(location, + "MLIR codegen encountered an error: dsp.highPassFilter " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], + operands[1]); } - if(callee == "getRangeOfVector"){ - if(call.getArgs().size() != 3){ - emitError(location, "MLIR codegen encountered an error: dsp.getRangeOfVector " - "accepts only 3 arguments"); + if (callee == "getRangeOfVector") { + if (call.getArgs().size() != 3) { + emitError(location, + "MLIR codegen encountered an error: dsp.getRangeOfVector " + "accepts only 3 arguments"); return nullptr; } - return builder.create(location, operands[0],operands[1], operands[2] ); + return builder.create(location, operands[0], + operands[1], operands[2]); } // FIRHammingOptimizedOp - if(callee == "FIRFilterHammingOptimized"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.FIRFilterHammingOptimized " - "accepts only 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1]); + if (callee == "FIRFilterHammingOptimized") { + if (call.getArgs().size() != 2) { + emitError( + location, + "MLIR codegen encountered an error: dsp.FIRFilterHammingOptimized " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], + operands[1]); } // HighPassFIRHammingOptimizedOp - if(callee == "highPassFIRHammingOptimized"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.HighPassFIRHammingOptimizedOp " - "accepts only 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1]); + if (callee == "highPassFIRHammingOptimized") { + if (call.getArgs().size() != 2) { + emitError(location, "MLIR codegen encountered an error: " + "dsp.HighPassFIRHammingOptimizedOp " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create( + location, operands[0], operands[1]); } // LMS FILTER - if(callee == "lmsFilter"){ - if(call.getArgs().size() != 5){ + if (callee == "lmsFilter") { + if (call.getArgs().size() != 5) { emitError(location, "MLIR codegen encountered an error: dsp.lmsFilter" "accepts only 5 arguments"); return nullptr; } - return builder.create(location, operands[0] , operands[1], operands[2], operands[3],operands[4] ); + return builder.create(location, operands[0], operands[1], + operands[2], operands[3], operands[4]); } - if(callee == "threshold"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.ThresholdOp " - "accepts only 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1]); + if (callee == "threshold") { + if (call.getArgs().size() != 2) { + emitError(location, + "MLIR codegen encountered an error: dsp.ThresholdOp " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); } - if(callee == "quantization"){ - if(call.getArgs().size() != 4){ - emitError(location, "MLIR codegen encountered an error: dsp.quantization " - "accepts only 4 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1],operands[2], operands[3]); + if (callee == "quantization") { + if (call.getArgs().size() != 4) { + emitError(location, + "MLIR codegen encountered an error: dsp.quantization " + "accepts only 4 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1], + operands[2], operands[3]); } - if(callee == "lmsFilterResponse"){ - if(call.getArgs().size() != 4){ + if (callee == "lmsFilterResponse") { + if (call.getArgs().size() != 4) { emitError(location, "MLIR codegen encountered an error: dsp.lmsFilter" "accepts only 4 arguments"); return nullptr; } - return builder.create(location, operands[0] , operands[1], operands[2], operands[3]); + return builder.create( + location, operands[0], operands[1], operands[2], operands[3]); } - if(callee == "runLenEncoding"){ - if(call.getArgs().size() != 1){ - emitError(location, "MLIR codegen encountered an error: dsp.runLenEncoding " - "accepts only 1 arguments"); - return nullptr; - } - return builder.create(location, operands[0]); + if (callee == "runLenEncoding") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.runLenEncoding " + "accepts only 1 arguments"); + return nullptr; + } + return builder.create(location, operands[0]); } - if(callee == "FIRFilterResSymmOptimized"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.FIRFilterResSymmOptimized " - "accepts only 2 arguments"); + if (callee == "FIRFilterResSymmOptimized") { + if (call.getArgs().size() != 2) { + emitError( + location, + "MLIR codegen encountered an error: dsp.FIRFilterResSymmOptimized " + "accepts only 2 arguments"); return nullptr; } - return builder.create(location, operands[0] , operands[1]); + return builder.create(location, operands[0], + operands[1]); } - if(callee == "len"){ - if(call.getArgs().size() != 1){ + if (callee == "len") { + if (call.getArgs().size() != 1) { emitError(location, "MLIR codegen encountered an error: dsp.len " "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0] ); + return builder.create(location, operands[0]); } - - if(callee == "reverseInput"){ - if(call.getArgs().size() != 1){ - emitError(location, "MLIR codegen encountered an error: dsp.reverseInput " - "accepts only 1 arguments"); + if (callee == "reverseInput") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.reverseInput " + "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0] ); + return builder.create(location, operands[0]); } - if(callee == "padding"){ - if(call.getArgs().size() != 3){ - emitError(location, "MLIR codegen encountered an error: dsp.padding " - "accepts only 3 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1], operands[2]); + if (callee == "padding") { + if (call.getArgs().size() != 3) { + emitError(location, "MLIR codegen encountered an error: dsp.padding " + "accepts only 3 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1], + operands[2]); } - if(callee == "FIRFilterYSymmOptimized"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.FIRFilterYSymmOptimizedOp " - "accepts only 2 arguments"); + if (callee == "FIRFilterYSymmOptimized") { + if (call.getArgs().size() != 2) { + emitError( + location, + "MLIR codegen encountered an error: dsp.FIRFilterYSymmOptimizedOp " + "accepts only 2 arguments"); return nullptr; } - return builder.create(location, operands[0] , operands[1]); + return builder.create(location, operands[0], + operands[1]); } - if(callee == "fft1DRealSymm"){ - if(call.getArgs().size() != 1){ - emitError(location, "MLIR codegen encountered an error: dsp.FFT1DRealSymmOp " - "accepts only 1 arguments"); + if (callee == "fft1DRealSymm") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.FFT1DRealSymmOp " + "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0] ); - } //FFT1DImgConjSymmOpLowering - if(callee == "fft1DimgConjSymm"){ - if(call.getArgs().size() != 1){ - emitError(location, "MLIR codegen encountered an error: dsp.FFT1DImgConjSymmOp " - "accepts only 1 arguments"); + return builder.create(location, operands[0]); + } // FFT1DImgConjSymmOpLowering + if (callee == "fft1DimgConjSymm") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.FFT1DImgConjSymmOp " + "accepts only 1 arguments"); return nullptr; } - return builder.create(location, operands[0] ); + return builder.create(location, operands[0]); } // Builtin calls have their custom operation, meaning this is a // straightforward emission. diff --git a/mlir/test/Examples/DspExample/dsp_bitwiseand_op.py b/mlir/test/Examples/DspExample/dsp_bitwiseand_op.py new file mode 100644 index 000000000000..ea513bdb7a9f --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_bitwiseand_op.py @@ -0,0 +1,20 @@ +# RUN: toyc-ch2 %s -emit=mlir 2>&1 | FileCheck %s + +def main() { + var a = [0,1,2,9,1000]; + var b = [2,2,2,15,100]; + var c = bitwiseand(a, b); + # c = [0,0,2,9,96] + print(c); +} +# ninja && ./bin/dsp1 ../mlir/test/Examples/DspExample/dsp_bitwiseand_op.py --emit=mlir + +# module { +# dsp.func @main() { +# %0 = dsp.constant dense<[0.000000e+00, 1.000000e+00, 2.000000e+00, 9.000000e+00, 1.000000e+03]> : tensor<5xf64> +# %1 = dsp.constant dense<[2.000000e+00, 2.000000e+00, 2.000000e+00, 1.500000e+01, 1.000000e+02]> : tensor<5xf64> +# %2 = dsp.bitwiseand %0, %1 : (tensor<5xf64>, tensor<5xf64>) -> tensor<*xf64> +# dsp.print %2 : tensor<*xf64> +# dsp.return +# } +# } diff --git a/mlir/test/Examples/DspExample/zeroCross/zeroCross.mlir b/mlir/test/Examples/DspExample/zeroCross/zeroCross.mlir new file mode 100644 index 000000000000..6a5b0e8d0a4e --- /dev/null +++ b/mlir/test/Examples/DspExample/zeroCross/zeroCross.mlir @@ -0,0 +1,46 @@ +func.func @main() { + %alloc = memref.alloc() : memref<3xf64> + %alloc_1 = memref.alloc() : memref + %c0 = arith.constant 0 : index + %c1 = arith.constant 1 : index + %c2 = arith.constant 2 : index + %cst = arith.constant 1.000000e+01 : f64 + affine.store %cst, %alloc[%c0] : memref<3xf64> + %cst_1 = arith.constant -1.000000e+01 : f64 + affine.store %cst_1, %alloc[%c1] : memref<3xf64> + %cst_2 = arith.constant 1.000000e+01 : f64 + affine.store %cst_2, %alloc[%c2] : memref<3xf64> + %lb = arith.constant 1 : index + %ub = arith.constant 3 : index + %step = arith.constant 1 : index + %total_0 = arith.constant 0.0 : f64 + %c3 = arith.constant 0 : i64 + %c4 = arith.constant 1.0 : f64 + %total = scf.for %arg0 = %lb to %ub step %step + iter_args(%total_iter = %total_0) -> (f64) { + %prev_idx = arith.subi %arg0, %step : index + %1 = memref.load %alloc[%prev_idx] : memref<3xf64> + %int_1 = arith.fptosi %1 : f64 to i64 + %sign_1 = arith.cmpi "slt", %int_1, %c3 : i64 + %2 = memref.load %alloc[%arg0] : memref<3xf64> + %int_2 = arith.fptosi %2 : f64 to i64 + %sign_2 = arith.cmpi "slt", %int_2, %c3 : i64 + %cond = arith.cmpi "eq", %sign_1, %sign_2 : i1 + %total_next = scf.if %cond -> (f64) { + scf.yield %total_iter : f64 + } else { + %new_total = arith.addf %total_iter, %c4 : f64 + scf.yield %new_total : f64 + } + scf.yield %total_next : f64 + } + + affine.store %total, %alloc_1[] : memref + // Print the value held by the buffer. + // dsp.print %alloc : memref<3xf64> + // Print the number of crosses through x=0 + dsp.print %alloc_1 : memref + memref.dealloc %alloc : memref<3xf64> + memref.dealloc %alloc_1 : memref + return +} \ No newline at end of file diff --git a/mlir/test/Examples/DspExample/zeroCross/zeroCross10.py b/mlir/test/Examples/DspExample/zeroCross/zeroCross10.py index 4c3848d0a300..24e0b57d45a4 100644 --- a/mlir/test/Examples/DspExample/zeroCross/zeroCross10.py +++ b/mlir/test/Examples/DspExample/zeroCross/zeroCross10.py @@ -1,33 +1,8 @@ # RUN: /bin/dsp1 %s -emit=mlir 2>&1 | FileCheck %s # User defined generic function that operates on unknown shaped arguments - -# def func1( x , y){ -# var z = x + y; -# return z; -# } - def main() { - var a = [10,20,30]; + var a = [10,-20,30,-10,40,50,60,-100,-20,-30,10]; # Count should be 6 var g = zeroCrossCount(a); - - print(g); } - -# CHECK-LABEL: toy.func @multiply_transpose( -# CHECK-SAME: [[VAL_0:%.*]]: tensor<*xf64>, [[VAL_1:%.*]]: tensor<*xf64>) -> tensor<*xf64> -# CHECK: [[VAL_2:%.*]] = toy.transpose([[VAL_0]] : tensor<*xf64>) to tensor<*xf64> -# CHECK-NEXT: [[VAL_3:%.*]] = toy.transpose([[VAL_1]] : tensor<*xf64>) to tensor<*xf64> -# CHECK-NEXT: [[VAL_4:%.*]] = toy.mul [[VAL_2]], [[VAL_3]] : tensor<*xf64> -# CHECK-NEXT: toy.return [[VAL_4]] : tensor<*xf64> - -# CHECK-LABEL: toy.func @main() -# CHECK-NEXT: [[VAL_5:%.*]] = toy.constant dense<{{\[\[}}1.000000e+00, 2.000000e+00, 3.000000e+00], [4.000000e+00, 5.000000e+00, 6.000000e+00]]> : tensor<2x3xf64> -# CHECK-NEXT: [[VAL_6:%.*]] = toy.reshape([[VAL_5]] : tensor<2x3xf64>) to tensor<2x3xf64> -# CHECK-NEXT: [[VAL_7:%.*]] = toy.constant dense<[1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00, 5.000000e+00, 6.000000e+00]> : tensor<6xf64> -# CHECK-NEXT: [[VAL_8:%.*]] = toy.reshape([[VAL_7]] : tensor<6xf64>) to tensor<2x3xf64> -# CHECK-NEXT: [[VAL_9:%.*]] = toy.generic_call @multiply_transpose([[VAL_6]], [[VAL_8]]) : (tensor<2x3xf64>, tensor<2x3xf64>) -> tensor<*xf64> -# CHECK-NEXT: [[VAL_10:%.*]] = toy.generic_call @multiply_transpose([[VAL_8]], [[VAL_6]]) : (tensor<2x3xf64>, tensor<2x3xf64>) -> tensor<*xf64> -# CHECK-NEXT: toy.print [[VAL_10]] : tensor<*xf64> -# CHECK-NEXT: toy.return From 06a87b306c4d2759bef50060f1a2432636c17d4f Mon Sep 17 00:00:00 2001 From: "Kuo, Mei-Chun" <94007620+Megan0704-1@users.noreply.github.com> Date: Fri, 18 Oct 2024 20:22:12 -0400 Subject: [PATCH 07/45] Conv2 dop (#11) * conv2d op * rebase latestMain --- .gitignore | 1 + .../dsp/SimpleBlocks/include/toy/Ops.td | 27 ++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 67 +++++ .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 176 +++++++++++--- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 9 + .../test/Examples/DspExample/conv2d/README.md | 5 + mlir/test/Examples/DspExample/conv2d/conv2d | Bin 0 -> 16408 bytes .../Examples/DspExample/conv2d/dsp_conv2d.c | 97 ++++++++ .../DspExample/conv2d/dsp_conv2d.mlir | 5 + .../DspExample/conv2d/dsp_conv2d_op.py | 18 ++ .../test/Examples/DspExample/conv2d/input.txt | 10 + mlir/test/conv2d/conv-affine.mlir | 149 ++++++++++++ mlir/test/conv2d/conv-index.mlir | 68 ++++++ mlir/test/conv2d/conv-llvm.ll | 230 ++++++++++++++++++ mlir/test/conv2d/conv.c | 95 ++++++++ mlir/test/conv2d/matmul.mlir | 20 ++ mlir/test/conv2d/scftest.mlir | 19 ++ mlir/test/conv2d/tosa.conv2d.mlir | 14 ++ mlir/test/mlir-opt/example1.mlir | 21 ++ 19 files changed, 996 insertions(+), 35 deletions(-) create mode 100644 mlir/test/Examples/DspExample/conv2d/README.md create mode 100755 mlir/test/Examples/DspExample/conv2d/conv2d create mode 100644 mlir/test/Examples/DspExample/conv2d/dsp_conv2d.c create mode 100644 mlir/test/Examples/DspExample/conv2d/dsp_conv2d.mlir create mode 100644 mlir/test/Examples/DspExample/conv2d/dsp_conv2d_op.py create mode 100644 mlir/test/Examples/DspExample/conv2d/input.txt create mode 100644 mlir/test/conv2d/conv-affine.mlir create mode 100644 mlir/test/conv2d/conv-index.mlir create mode 100644 mlir/test/conv2d/conv-llvm.ll create mode 100644 mlir/test/conv2d/conv.c create mode 100644 mlir/test/conv2d/matmul.mlir create mode 100644 mlir/test/conv2d/scftest.mlir create mode 100644 mlir/test/conv2d/tosa.conv2d.mlir create mode 100644 mlir/test/mlir-opt/example1.mlir diff --git a/.gitignore b/.gitignore index a68b971cdb5e..8b3186a6eea4 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ # Explicit files to ignore (only matches one). #==============================================================================# # Various tag programs +tags /tags /TAGS /GPATH diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 09c284261c6b..6aea97bdf926 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -1989,4 +1989,31 @@ def FFT1DImgConjSymmOp : Dsp_Op<"fft1DimgConjSymm", let hasVerifier = 1; } +//===----------------------------------------------------------------------===// +// Conv2DOp +//===----------------------------------------------------------------------===// + +def Conv2DOp : Dsp_Op<"conv2d", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Dsp dialect convolution 2d operation"; + let description = [{ + Performs a 2D convolution on the input tensor using specified kernel. + }]; + + let arguments = (ins F64Tensor:$input, F64Tensor:$kernel, F64Tensor:$bias); + + let results = (outs F64Tensor:$output); + + let builders = [ + OpBuilder<(ins "Value":$input, "Value":$kernel, "Value":$bias)> + ]; + + let extraClassDeclaration = [{ + static StringRef getStrideName() { return "stride"; } + static StringRef getPaddingName() { return "padding"; } + }]; + + let hasVerifier = 1; +} + #endif // TOY_OPS diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index 5c0ea044a93e..eacf6a2e3421 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -2520,6 +2520,73 @@ mlir::LogicalResult FFT1DImgConjSymmOp::verify() { return mlir::success(); } +//===----------------------------------------------------------------------===// +// Conv2DOp +//===----------------------------------------------------------------------===// + +void Conv2DOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value input, mlir::Value weight, mlir::Value bias) { + state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); + state.addOperands({input, weight, bias}); +} +void Conv2DOp::inferShapes() { + auto inputType = llvm::dyn_cast(getInput().getType()); + auto kernelType = llvm::dyn_cast(getKernel().getType()); + + int64_t IH = inputType.getShape()[0]; + int64_t IW = inputType.getShape()[1]; + int64_t KH = kernelType.getShape()[0]; + int64_t KW = kernelType.getShape()[1]; + int64_t OH = IH-KH+1, OW=IW-KW+1; + + SmallVector dims = {OH, OW}; + getResult().setType(RankedTensorType::get(dims, inputType.getElementType())); +} + +mlir::LogicalResult Conv2DOp::verify() { + + auto inputType = llvm::dyn_cast(getInput().getType()); + auto kernelType = llvm::dyn_cast(getKernel().getType()); + auto biasType = llvm::dyn_cast(getBias().getType()); + + if(!inputType) { + llvm::errs() << "expect a ranked tensor for input, get " << getInput(); + return mlir::failure(); + } + if(!kernelType) { + llvm::errs() << "expect a ranked tensor for kernel, get " << getKernel(); + return mlir::failure(); + } + if(!biasType) { + llvm::errs() << "expect a one dimensional ranked tensor for bias, get " << getBias(); + return mlir::failure(); + } + + auto inputRank = inputType.getRank(); + auto kernelRank = kernelType.getRank(); + + if(inputRank != 2 ) { + llvm::errs() << "expect 2 dimensional input, format N IH IW IC, get " << inputRank; + return mlir::failure(); + } + if(kernelRank != 2 ) { + llvm::errs() << "expect 2 dimensional kernel, format OC KH KW IC."; + return mlir::failure(); + } + + if(inputType.getShape()[0] < kernelType.getShape()[0]) { + llvm::errs() << "input shape < kernel shape at 1st dimension"; + return mlir::failure(); + } + + if(inputType.getShape()[1] < kernelType.getShape()[1]) { + llvm::errs() << "input shape < kernel shape at 2nd dimension"; + return mlir::failure(); + } + + return mlir::success(); +} + //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 8c16e1d5b58d..4e4efe5de105 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -7106,6 +7106,112 @@ struct TransposeOpLowering : public ConversionPattern { } }; +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: Transpose operations +//===----------------------------------------------------------------------===// + +struct Conv2DOpLowering : public ConversionPattern { + Conv2DOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::Conv2DOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + + auto loc = op->getLoc(); + // output mem alloc and dealloc + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); + + Conv2DOpAdaptor conv2dAdaptor(operands); + Value input = conv2dAdaptor.getInput(); + Value kernel = conv2dAdaptor.getKernel(); + + // ranked tensor type + auto inputType = llvm::dyn_cast(op->getOperand(0).getType()); + auto kernelType = llvm::dyn_cast(op->getOperand(1).getType()); + + ArrayRef inputShape = inputType.getShape(); + ArrayRef kernelShape = kernelType.getShape(); + + // input layout + int64_t IH = inputShape[0]; + int64_t IW = inputShape[1]; + + // kernel layout + int64_t KH = kernelShape[0]; + int64_t KW = kernelShape[1]; + + // output layout + ArrayRef outputShape = output.getShape(); + int64_t OH = outputShape[0]; + int64_t OW = outputShape[1]; + + + AffineExpr d0, d1, d2, d3; // declare affine expression: i, j, p, q + bindDims(rewriter.getContext(), d0, d1, d2, d3); // bind affine expr d0, d1 to current input dimension i, j, p, q + + // input affine map + AffineMap inputMap = AffineMap::get(4, 0, ArrayRef{d0+d2, d1+d3}, rewriter.getContext()); + // kernel affine map + AffineMap kernelMap = AffineMap::get(4, 0, ArrayRef{d2, d3}, rewriter.getContext()); + + // loops + int64_t lb=0, step=1; + /* looping i*/ + AffineForOp forOpI = rewriter.create(loc, lb, OH, step); + rewriter.setInsertionPointToStart(forOpI.getBody()); + auto ivI = forOpI.getInductionVar(); + + /* looping j*/ + AffineForOp forOpJ = rewriter.create(loc, lb, OW, step); + rewriter.setInsertionPointToStart(forOpJ.getBody()); + auto ivJ = forOpJ.getInductionVar(); + + // initilize output val + Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + rewriter.create(loc, zeroVal, alloc, ValueRange{ivI, ivJ}); + + /* looping p*/ + AffineForOp forOpP = rewriter.create(loc, lb, KH, step); + rewriter.setInsertionPointToStart(forOpP.getBody()); + auto ivP = forOpP.getInductionVar(); + + /* looping q*/ + AffineForOp forOpQ = rewriter.create(loc, lb, KW, step); + rewriter.setInsertionPointToStart(forOpQ.getBody()); + auto ivQ = forOpQ.getInductionVar(); + + // input bound check + Value inputRow = rewriter.create(loc, inputMap.getSubMap(0), ValueRange{ivI, ivJ, ivP, ivQ}); + Value inputCol = rewriter.create(loc, inputMap.getSubMap(1), ValueRange{ivI, ivJ, ivP, ivQ}); + Value rowUB = rewriter.create(loc, arith::CmpIPredicate::slt, inputRow, rewriter.create(loc, IH)); + Value colUB = rewriter.create(loc, arith::CmpIPredicate::slt, inputCol, rewriter.create(loc, IW)); + Value bound = rewriter.create(loc, rowUB, colUB); + + // bound condition + rewriter.create(loc, bound, [&](OpBuilder &builder, Location loc) { + // load input + Value inputVal = builder.create(loc, input, inputMap,ValueRange{ivI, ivJ, ivP, ivQ}); + Value kernelVal = builder.create(loc, kernel, kernelMap, ValueRange{ivI, ivJ, ivP, ivQ}); + // mul + Value prod = builder.create(loc, inputVal, kernelVal); + Value outputVal = builder.create(loc, alloc, ValueRange{ivI, ivJ}); + Value sum = builder.create(loc, prod, outputVal); + + // store the computed output + builder.create(loc, sum, alloc, ValueRange{ivI, ivJ}); + + builder.create(loc); + }); + + rewriter.replaceOp(op, alloc); + + return success(); + } +}; // conv2d + } // namespace //===----------------------------------------------------------------------===// @@ -7116,9 +7222,9 @@ struct TransposeOpLowering : public ConversionPattern { /// computationally intensive (like matmul for example...) while keeping the /// rest of the code in the Toy dialect. namespace { -struct ToyToAffineLoweringPass - : public PassWrapper> { - MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ToyToAffineLoweringPass) + struct ToyToAffineLoweringPass + : public PassWrapper> { + MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ToyToAffineLoweringPass) void getDependentDialects(DialectRegistry ®istry) const override { registry @@ -7130,29 +7236,29 @@ struct ToyToAffineLoweringPass } // namespace void ToyToAffineLoweringPass::runOnOperation() { - // The first thing to define is the conversion target. This will define the - // final target for this lowering. - ConversionTarget target(getContext()); - - // We define the specific operations, or dialects, that are legal targets for - // this lowering. In our case, we are lowering to a combination of the - // `Affine`, `Arith`, `Func`, and `MemRef` dialects. - target.addLegalDialect(); - - // We also define the Toy dialect as Illegal so that the conversion will fail - // if any of these operations are *not* converted. Given that we actually want - // a partial lowering, we explicitly mark the Toy operations that don't want - // to lower, `dsp.print`, as `legal`. `dsp.print` will still need its operands - // to be updated though (as we convert from TensorType to MemRefType), so we - // only treat it as `legal` if its operands are legal. - target.addIllegalDialect(); - target.addDynamicallyLegalOp([](dsp::PrintOp op) { - return llvm::none_of(op->getOperandTypes(), - [](Type type) { return llvm::isa(type); }); - }); + // The first thing to define is the conversion target. This will define the + // final target for this lowering. + ConversionTarget target(getContext()); + + // We define the specific operations, or dialects, that are legal targets for + // this lowering. In our case, we are lowering to a combination of the + // `Affine`, `Arith`, `Func`, and `MemRef` dialects. + target.addLegalDialect(); + + // We also define the Toy dialect as Illegal so that the conversion will fail + // if any of these operations are *not* converted. Given that we actually want + // a partial lowering, we explicitly mark the Toy operations that don't want + // to lower, `dsp.print`, as `legal`. `dsp.print` will still need its operands + // to be updated though (as we convert from TensorType to MemRefType), so we + // only treat it as `legal` if its operands are legal. + target.addIllegalDialect(); + target.addDynamicallyLegalOp([](dsp::PrintOp op) { + return llvm::none_of(op->getOperandTypes(), + [](Type type) { return llvm::isa(type); }); + }); // Now that the conversion target has been defined, we just need to provide // the set of patterns that will lower the Toy operations. @@ -7175,20 +7281,20 @@ void ToyToAffineLoweringPass::runOnOperation() { RunLenEncodingOpLowering, FIRFilterResSymmOptimizedOpLowering, LengthOpLowering, ReverseInputOpLowering, PaddingOpLowering, FIRFilterYSymmOptimizedOpLowering, FFT1DRealSymmOpLowering, - FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering>(&getContext()); - - // With the target and rewrite patterns defined, we can now attempt the - // conversion. The conversion will signal failure if any of our `illegal` - // operations were not converted successfully. - if (failed( - applyPartialConversion(getOperation(), target, std::move(patterns)))) - signalPassFailure(); + FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering>(&getContext()); + + // With the target and rewrite patterns defined, we can now attempt the + // conversion. The conversion will signal failure if any of our `illegal` + // operations were not converted successfully. + if (failed( + applyPartialConversion(getOperation(), target, std::move(patterns)))) + signalPassFailure(); } /// Create a pass for lowering operations in the `Affine` and `Std` dialects, /// for a subset of the Toy IR (e.g. matmul). std::unique_ptr mlir::dsp::createLowerToAffinePass() { - return std::make_unique(); + return std::make_unique(); } #pragma GCC diagnostic pop diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index 2e9e5085d7d9..c2f84bef1401 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -801,6 +801,15 @@ class MLIRGenImpl { } return builder.create(location, operands[0]); } + + if(callee == "conv2d") { + if(call.getArgs().size() != 3) { + emitError(location, "MLIR codegen encountered an error: dsp.Conv2DOp " + "accepts 3 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1], operands[2]); + } // Builtin calls have their custom operation, meaning this is a // straightforward emission. // if(callee == "delay"){ diff --git a/mlir/test/Examples/DspExample/conv2d/README.md b/mlir/test/Examples/DspExample/conv2d/README.md new file mode 100644 index 000000000000..978311729bb0 --- /dev/null +++ b/mlir/test/Examples/DspExample/conv2d/README.md @@ -0,0 +1,5 @@ +Convolution Formula +$$ +output(i, j) = \sum^{k-1}_{p=0}\sum^{k-1}_{q=0}input(i+p, j+q) * kernel(p, q) +$$ + diff --git a/mlir/test/Examples/DspExample/conv2d/conv2d b/mlir/test/Examples/DspExample/conv2d/conv2d new file mode 100755 index 0000000000000000000000000000000000000000..7a7ce664e382905da5630ef2a871eecad0f51cb0 GIT binary patch literal 16408 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 literal 0 HcmV?d00001 diff --git a/mlir/test/Examples/DspExample/conv2d/dsp_conv2d.c b/mlir/test/Examples/DspExample/conv2d/dsp_conv2d.c new file mode 100644 index 000000000000..e5462e61c038 --- /dev/null +++ b/mlir/test/Examples/DspExample/conv2d/dsp_conv2d.c @@ -0,0 +1,97 @@ +#include +#include + +void freeArr(int size, int** arr) { + for(int i=0; i= INPUTSIZE || j+q >= INPUTSIZE) continue; + c[i][j] += a[i+p][j+q] * b[p][q]; + + } + } + } + } +} + +int main() { + + const char* filename = "input.txt"; + FILE *file = fopen(filename, "r"); + + if(file == NULL) { + printf("error opening file"); + exit(1); + } + int* inputrows, *inputcols, *kernelrows, *kernelcols; + int rows=0, cols=0; + fscanf(file, "%d %d", &rows, &cols); + + inputrows = &rows; + inputcols = &cols; + + printf("input size %d, %d\n", *inputrows, *inputcols); + int** a = (int**) malloc((*inputrows)*sizeof(int*)); + for(int i=0; i<(*inputrows); ++i) { + a[i] = (int*) malloc((*inputcols)*sizeof(int)); + } + + for(int i=0; i<(*inputrows); ++i) { + for(int j=0; j<(*inputcols); ++j) { + fscanf(file, "%d ", &a[i][j]); + } + } + + int krows=0, kcols=0; + fscanf(file, "%d %d", &krows, &kcols); + + kernelrows = &krows; + kernelcols = &kcols; + + printf("kernel size %d, %d\n", *kernelrows, *kernelcols); + int** b = (int**) malloc((*kernelrows)*sizeof(int*)); + for(int i=0; i<(*kernelrows); ++i) { + b[i] = (int*) malloc((*kernelcols)*sizeof(int)); + } + + for(int i=0; i<(*kernelrows); ++i) { + for(int j=0; j<(*kernelcols); ++j) { + fscanf(file, "%d", &b[i][j]); + } + } + + fclose(file); + + + int outputrows = (*inputrows) - (*kernelrows) +1; + int outputcols = (*inputcols) - (*kernelcols) +1; + int** c = (int**) malloc((outputrows)*sizeof(int*)); + for(int i=0; i + %kernel = memref.alloc() : memref<3x3xf64> + %output = memref.alloc() : memref<4x4xf64> + + %c0 = arith.constant 0 : index + %c1 = arith.constant 1 : index + %c2 = arith.constant 2 : index + %c3 = arith.constant 3 : index + %c4 = arith.constant 4 : index + + %cst0 = arith.constant 0.000000e+00 : f64 + %cst1 = arith.constant 1.000000e+00 : f64 + %cst2 = arith.constant 2.000000e+00 : f64 + %cst3 = arith.constant 3.000000e+00 : f64 + %cst4 = arith.constant 4.000000e+00 : f64 + %cst5 = arith.constant 5.000000e+00 : f64 + %cst6 = arith.constant 6.000000e+00 : f64 + %cst7 = arith.constant 7.000000e+00 : f64 + %cst8 = arith.constant 8.000000e+00 : f64 + %cstn1float = arith.constant -1.000000e+00 : f64 + %cstn1int = arith.constant -1 : i64 + + // input + affine.store %cst1, %input[%c0, %c0] : memref<4x4xf64> + affine.store %cst2, %input[%c0, %c1] : memref<4x4xf64> + affine.store %cst3, %input[%c0, %c2] : memref<4x4xf64> + affine.store %cst4, %input[%c0, %c3] : memref<4x4xf64> + + affine.store %cst2, %input[%c1, %c0] : memref<4x4xf64> + affine.store %cst3, %input[%c1, %c1] : memref<4x4xf64> + affine.store %cst4, %input[%c1, %c2] : memref<4x4xf64> + affine.store %cst6, %input[%c1, %c3] : memref<4x4xf64> + + affine.store %cst4, %input[%c2, %c0] : memref<4x4xf64> + affine.store %cst3, %input[%c2, %c1] : memref<4x4xf64> + affine.store %cst2, %input[%c2, %c2] : memref<4x4xf64> + affine.store %cst1, %input[%c2, %c3] : memref<4x4xf64> + + affine.store %cst6, %input[%c3, %c0] : memref<4x4xf64> + affine.store %cst8, %input[%c3, %c1] : memref<4x4xf64> + affine.store %cst4, %input[%c3, %c2] : memref<4x4xf64> + affine.store %cst7, %input[%c3, %c3] : memref<4x4xf64> + + // dsp.print %input : memref<4x4xf64> + + // kernel + affine.store %cst1, %kernel[%c0, %c0] : memref<3x3xf64> + affine.store %cst0, %kernel[%c0, %c1] : memref<3x3xf64> + affine.store %cstn1float, %kernel[%c0, %c2] : memref<3x3xf64> + + affine.store %cst1, %kernel[%c1, %c0] : memref<3x3xf64> + affine.store %cst0, %kernel[%c1, %c1] : memref<3x3xf64> + affine.store %cstn1float, %kernel[%c1, %c2] : memref<3x3xf64> + + affine.store %cst1, %kernel[%c2, %c0] : memref<3x3xf64> + affine.store %cst0, %kernel[%c2, %c1] : memref<3x3xf64> + affine.store %cstn1float, %kernel[%c2, %c2] : memref<3x3xf64> + + + // delta + %delta_ub = arith.divf %cst3, %cst2 : f64 + %delta_lb = arith.mulf %delta_ub, %cstn1float : f64 + + %ub = arith.fptosi %delta_ub : f64 to i64 + %lb = arith.fptosi %delta_lb : f64 to i64 + + // %delta_dim_ub = arith.index_cast %ub : i64 to index + // %delta_dim_lb = arith.index_cast %lb : i64 to index + %delta_dim_lb = arith.constant -1 : index + %delta_dim_ub = arith.constant 1 : index + + // for debug + %i = memref.alloc() : memref<1xi64> + %d = memref.alloc() : memref<1xf64> + memref.store %cstn1float, %d[%c0] : memref<1xf64> + memref.store %cstn1int, %i[%c0] : memref<1xi64> + dsp.print %d : memref<1xf64> + dsp.print %i : memref<1xi64> + + // x, y iteration + scf.for %x = %c0 to %c4 step %c1 { + scf.for %y = %c0 to %c4 step %c1 { + %mat_sum = scf.for %kx = %delta_dim_lb to %delta_dim_ub step %c1 iter_args(%outer_sum = %cst0) -> ( f64 ) { + %ele_sum = scf.for %ky = %delta_dim_lb to %delta_dim_ub step %c1 iter_args(%inner_sum = %outer_sum) -> ( f64 ) { + %img_x = arith.addi %x, %kx: index + %img_y = arith.addi %y, %ky: index + + %test = arith.index_cast %kx : index to i64 + memref.store %test, %i[%c0] : memref<1xi64> + // dsp.print %i : memref<1xi64> + + // sge : predicate 5 + %cond_x_lb = "arith.cmpi"(%img_x, %c0) {predicate=5: i64} : (index, index) -> i1 + %cond_y_lb = "arith.cmpi"(%img_y, %c0) {predicate=5: i64} : (index, index) -> i1 + // slt + %cond_x_ub = "arith.cmpi"(%img_x, %c4) {predicate=2: i64} : (index, index) -> i1 + %cond_y_ub = "arith.cmpi"(%img_y, %c4) {predicate=2: i64} : (index, index) -> i1 + + %img_sum_ = scf.if %cond_x_lb -> (f64) { + %sum__ = scf.if %cond_y_lb -> (f64) { + %sum_ = scf.if %cond_x_ub -> (f64) { + %sum = scf.if %cond_y_ub -> (f64) { + // load from input + %input_val = memref.load %input[%img_x, %img_y] : memref<4x4xf64> + + // load from kernel + %ker_x = arith.addi %kx, %delta_dim_ub : index + %ker_y = arith.addi %ky, %delta_dim_ub : index + %kernel_val = memref.load %kernel[%ker_x, %ker_y] : memref<3x3xf64> + + %img_prod = arith.mulf %input_val, %kernel_val : f64 + scf.yield %img_prod : f64 + } else { + scf.yield %cst0 : f64 + } + scf.yield %sum : f64 + } else { + scf.yield %cst0 : f64 + } + scf.yield %sum_ : f64 + } else { + scf.yield %cst0 : f64 + } + scf.yield %sum__ : f64 + }else{ + scf.yield %cst0 : f64 + } + + %IMGSUM = arith.addf %inner_sum, %img_sum_ : f64 + scf.yield %IMGSUM : f64 + } + + scf.yield %ele_sum : f64 + } + memref.store %mat_sum, %output[%x, %y] : memref<4x4xf64> + } + } + // dsp.print %input : memref<4x4xf64> + // dsp.print %kernel : memref<3x3xf64> + // dsp.print %output : memref<4x4xf64> + + memref.dealloc %input : memref<4x4xf64> + memref.dealloc %kernel : memref<3x3xf64> + memref.dealloc %output : memref<4x4xf64> + return + } +} diff --git a/mlir/test/conv2d/conv-index.mlir b/mlir/test/conv2d/conv-index.mlir new file mode 100644 index 000000000000..46c8ab486040 --- /dev/null +++ b/mlir/test/conv2d/conv-index.mlir @@ -0,0 +1,68 @@ +module { + func.func @main() { + %input = memref.alloc() : memref<4x4xf32> + %kernel = memref.alloc() : memref<3x3xf32> + %output = memref.alloc() : memref<4x4xf32> + + %c0 = index.constant 0 + %c1 = index.constant 1 + %c2 = index.constant 2 + %c3 = index.constant 3 + + %cst0 = arith.constant 0.000000e+00 : f32 + %cst1 = arith.constant 1.000000e+00 : f32 + %cst2 = arith.constant 2.000000e+00 : f32 + %cst3 = arith.constant 3.000000e+00 : f32 + %cst4 = arith.constant 4.000000e+00 : f32 + %cst5 = arith.constant 5.000000e+00 : f32 + %cst6 = arith.constant 6.000000e+00 : f32 + %cst7 = arith.constant 7.000000e+00 : f32 + %cst8 = arith.constant 8.000000e+00 : f32 + %cstn1 = arith.constant -1.000000e+00 : f32 + + // input + affine.store %cst1, %input[%c0, %c0] : memref<4x4xf32> + affine.store %cst2, %input[%c0, %c1] : memref<4x4xf32> + affine.store %cst3, %input[%c0, %c2] : memref<4x4xf32> + affine.store %cst4, %input[%c0, %c3] : memref<4x4xf32> + + affine.store %cst2, %input[%c1, %c0] : memref<4x4xf32> + affine.store %cst3, %input[%c1, %c1] : memref<4x4xf32> + affine.store %cst4, %input[%c1, %c2] : memref<4x4xf32> + affine.store %cst6, %input[%c1, %c3] : memref<4x4xf32> + + affine.store %cst4, %input[%c2, %c0] : memref<4x4xf32> + affine.store %cst3, %input[%c2, %c1] : memref<4x4xf32> + affine.store %cst2, %input[%c2, %c2] : memref<4x4xf32> + affine.store %cst1, %input[%c2, %c3] : memref<4x4xf32> + + affine.store %cst6, %input[%c3, %c0] : memref<4x4xf32> + affine.store %cst8, %input[%c3, %c1] : memref<4x4xf32> + affine.store %cst4, %input[%c3, %c2] : memref<4x4xf32> + affine.store %cst7, %input[%c3, %c3] : memref<4x4xf32> + + // kernel + affine.store %cst1, %kernel[%c0, %c0] : memref<3x3xf32> + affine.store %cst0, %kernel[%c0, %c1] : memref<3x3xf32> + affine.store %cstn1, %kernel[%c0, %c2] : memref<3x3xf32> + + affine.store %cst1, %kernel[%c1, %c0] : memref<3x3xf32> + affine.store %cst0, %kernel[%c1, %c1] : memref<3x3xf32> + affine.store %cstn1, %kernel[%c1, %c2] : memref<3x3xf32> + + affine.store %cst1, %kernel[%c2, %c0] : memref<3x3xf32> + affine.store %cst0, %kernel[%c2, %c1] : memref<3x3xf32> + affine.store %cstn1, %kernel[%c2, %c2] : memref<3x3xf32> + + + // delta + %delta = arith.divf %cst3, %cst2 : f32 + %delta_i = arith.fptoui %delta : f32 to i32 + %delta_dim = arith.index_cast %delta_i : i32 to index + + memref.dealloc %input : memref<4x4xf32> + memref.dealloc %kernel : memref<3x3xf32> + memref.dealloc %output : memref<4x4xf32> + return + } +} diff --git a/mlir/test/conv2d/conv-llvm.ll b/mlir/test/conv2d/conv-llvm.ll new file mode 100644 index 000000000000..45d413b4ce44 --- /dev/null +++ b/mlir/test/conv2d/conv-llvm.ll @@ -0,0 +1,230 @@ +llvm.func @free(!llvm.ptr) +llvm.func @malloc(i64) -> !llvm.ptr +llvm.func @main() { + %0 = llvm.mlir.constant(4 : index) : i64 + %1 = llvm.mlir.constant(4 : index) : i64 + %2 = llvm.mlir.constant(1 : index) : i64 + %3 = llvm.mlir.constant(16 : index) : i64 + %4 = llvm.mlir.zero : !llvm.ptr + %5 = llvm.getelementptr %4[%3] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + %6 = llvm.ptrtoint %5 : !llvm.ptr to i64 + %7 = llvm.call @malloc(%6) : (i64) -> !llvm.ptr + %8 = llvm.mlir.undef : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %9 = llvm.insertvalue %7, %8[0] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %10 = llvm.insertvalue %7, %9[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %11 = llvm.mlir.constant(0 : index) : i64 + %12 = llvm.insertvalue %11, %10[2] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %13 = llvm.insertvalue %0, %12[3, 0] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %14 = llvm.insertvalue %1, %13[3, 1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %15 = llvm.insertvalue %1, %14[4, 0] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %16 = llvm.insertvalue %2, %15[4, 1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %17 = llvm.mlir.constant(3 : index) : i64 + %18 = llvm.mlir.constant(3 : index) : i64 + %19 = llvm.mlir.constant(1 : index) : i64 + %20 = llvm.mlir.constant(9 : index) : i64 + %21 = llvm.mlir.zero : !llvm.ptr + %22 = llvm.getelementptr %21[%20] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + %23 = llvm.ptrtoint %22 : !llvm.ptr to i64 + %24 = llvm.call @malloc(%23) : (i64) -> !llvm.ptr + %25 = llvm.mlir.undef : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %26 = llvm.insertvalue %24, %25[0] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %27 = llvm.insertvalue %24, %26[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %28 = llvm.mlir.constant(0 : index) : i64 + %29 = llvm.insertvalue %28, %27[2] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %30 = llvm.insertvalue %17, %29[3, 0] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %31 = llvm.insertvalue %18, %30[3, 1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %32 = llvm.insertvalue %18, %31[4, 0] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %33 = llvm.insertvalue %19, %32[4, 1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %34 = llvm.mlir.constant(4 : index) : i64 + %35 = llvm.mlir.constant(4 : index) : i64 + %36 = llvm.mlir.constant(1 : index) : i64 + %37 = llvm.mlir.constant(16 : index) : i64 + %38 = llvm.mlir.zero : !llvm.ptr + %39 = llvm.getelementptr %38[%37] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + %40 = llvm.ptrtoint %39 : !llvm.ptr to i64 + %41 = llvm.call @malloc(%40) : (i64) -> !llvm.ptr + %42 = llvm.mlir.undef : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %43 = llvm.insertvalue %41, %42[0] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %44 = llvm.insertvalue %41, %43[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %45 = llvm.mlir.constant(0 : index) : i64 + %46 = llvm.insertvalue %45, %44[2] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %47 = llvm.insertvalue %34, %46[3, 0] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %48 = llvm.insertvalue %35, %47[3, 1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %49 = llvm.insertvalue %35, %48[4, 0] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %50 = llvm.insertvalue %36, %49[4, 1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %51 = llvm.mlir.constant(0 : i64) : i64 + %52 = llvm.mlir.constant(1 : i64) : i64 + %53 = llvm.mlir.constant(2 : i64) : i64 + %54 = llvm.mlir.constant(3 : i64) : i64 + %55 = llvm.mlir.constant(0.000000e+00 : f32) : f32 + %56 = llvm.mlir.constant(1.000000e+00 : f32) : f32 + %57 = llvm.mlir.constant(2.000000e+00 : f32) : f32 + %58 = llvm.mlir.constant(3.000000e+00 : f32) : f32 + %59 = llvm.mlir.constant(4.000000e+00 : f32) : f32 + %60 = llvm.mlir.constant(5.000000e+00 : f32) : f32 + %61 = llvm.mlir.constant(6.000000e+00 : f32) : f32 + %62 = llvm.mlir.constant(7.000000e+00 : f32) : f32 + %63 = llvm.mlir.constant(8.000000e+00 : f32) : f32 + %64 = llvm.mlir.constant(-1.000000e+00 : f32) : f32 + %65 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %66 = llvm.mlir.constant(4 : index) : i64 + %67 = llvm.mul %51, %66 : i64 + %68 = llvm.add %67, %51 : i64 + %69 = llvm.getelementptr %65[%68] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %56, %69 : f32, !llvm.ptr + %70 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %71 = llvm.mlir.constant(4 : index) : i64 + %72 = llvm.mul %51, %71 : i64 + %73 = llvm.add %72, %52 : i64 + %74 = llvm.getelementptr %70[%73] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %57, %74 : f32, !llvm.ptr + %75 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %76 = llvm.mlir.constant(4 : index) : i64 + %77 = llvm.mul %51, %76 : i64 + %78 = llvm.add %77, %53 : i64 + %79 = llvm.getelementptr %75[%78] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %58, %79 : f32, !llvm.ptr + %80 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %81 = llvm.mlir.constant(4 : index) : i64 + %82 = llvm.mul %51, %81 : i64 + %83 = llvm.add %82, %54 : i64 + %84 = llvm.getelementptr %80[%83] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %59, %84 : f32, !llvm.ptr + %85 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %86 = llvm.mlir.constant(4 : index) : i64 + %87 = llvm.mul %52, %86 : i64 + %88 = llvm.add %87, %51 : i64 + %89 = llvm.getelementptr %85[%88] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %57, %89 : f32, !llvm.ptr + %90 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %91 = llvm.mlir.constant(4 : index) : i64 + %92 = llvm.mul %52, %91 : i64 + %93 = llvm.add %92, %52 : i64 + %94 = llvm.getelementptr %90[%93] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %58, %94 : f32, !llvm.ptr + %95 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %96 = llvm.mlir.constant(4 : index) : i64 + %97 = llvm.mul %52, %96 : i64 + %98 = llvm.add %97, %53 : i64 + %99 = llvm.getelementptr %95[%98] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %59, %99 : f32, !llvm.ptr + %100 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %101 = llvm.mlir.constant(4 : index) : i64 + %102 = llvm.mul %52, %101 : i64 + %103 = llvm.add %102, %54 : i64 + %104 = llvm.getelementptr %100[%103] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %61, %104 : f32, !llvm.ptr + %105 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %106 = llvm.mlir.constant(4 : index) : i64 + %107 = llvm.mul %53, %106 : i64 + %108 = llvm.add %107, %51 : i64 + %109 = llvm.getelementptr %105[%108] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %59, %109 : f32, !llvm.ptr + %110 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %111 = llvm.mlir.constant(4 : index) : i64 + %112 = llvm.mul %53, %111 : i64 + %113 = llvm.add %112, %52 : i64 + %114 = llvm.getelementptr %110[%113] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %58, %114 : f32, !llvm.ptr + %115 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %116 = llvm.mlir.constant(4 : index) : i64 + %117 = llvm.mul %53, %116 : i64 + %118 = llvm.add %117, %53 : i64 + %119 = llvm.getelementptr %115[%118] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %57, %119 : f32, !llvm.ptr + %120 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %121 = llvm.mlir.constant(4 : index) : i64 + %122 = llvm.mul %53, %121 : i64 + %123 = llvm.add %122, %54 : i64 + %124 = llvm.getelementptr %120[%123] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %56, %124 : f32, !llvm.ptr + %125 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %126 = llvm.mlir.constant(4 : index) : i64 + %127 = llvm.mul %54, %126 : i64 + %128 = llvm.add %127, %51 : i64 + %129 = llvm.getelementptr %125[%128] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %61, %129 : f32, !llvm.ptr + %130 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %131 = llvm.mlir.constant(4 : index) : i64 + %132 = llvm.mul %54, %131 : i64 + %133 = llvm.add %132, %52 : i64 + %134 = llvm.getelementptr %130[%133] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %63, %134 : f32, !llvm.ptr + %135 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %136 = llvm.mlir.constant(4 : index) : i64 + %137 = llvm.mul %54, %136 : i64 + %138 = llvm.add %137, %53 : i64 + %139 = llvm.getelementptr %135[%138] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %59, %139 : f32, !llvm.ptr + %140 = llvm.extractvalue %16[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %141 = llvm.mlir.constant(4 : index) : i64 + %142 = llvm.mul %54, %141 : i64 + %143 = llvm.add %142, %54 : i64 + %144 = llvm.getelementptr %140[%143] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %62, %144 : f32, !llvm.ptr + %145 = llvm.extractvalue %33[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %146 = llvm.mlir.constant(3 : index) : i64 + %147 = llvm.mul %51, %146 : i64 + %148 = llvm.add %147, %51 : i64 + %149 = llvm.getelementptr %145[%148] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %56, %149 : f32, !llvm.ptr + %150 = llvm.extractvalue %33[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %151 = llvm.mlir.constant(3 : index) : i64 + %152 = llvm.mul %51, %151 : i64 + %153 = llvm.add %152, %52 : i64 + %154 = llvm.getelementptr %150[%153] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %55, %154 : f32, !llvm.ptr + %155 = llvm.extractvalue %33[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %156 = llvm.mlir.constant(3 : index) : i64 + %157 = llvm.mul %51, %156 : i64 + %158 = llvm.add %157, %53 : i64 + %159 = llvm.getelementptr %155[%158] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %64, %159 : f32, !llvm.ptr + %160 = llvm.extractvalue %33[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %161 = llvm.mlir.constant(3 : index) : i64 + %162 = llvm.mul %52, %161 : i64 + %163 = llvm.add %162, %51 : i64 + %164 = llvm.getelementptr %160[%163] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %56, %164 : f32, !llvm.ptr + %165 = llvm.extractvalue %33[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %166 = llvm.mlir.constant(3 : index) : i64 + %167 = llvm.mul %52, %166 : i64 + %168 = llvm.add %167, %52 : i64 + %169 = llvm.getelementptr %165[%168] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %55, %169 : f32, !llvm.ptr + %170 = llvm.extractvalue %33[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %171 = llvm.mlir.constant(3 : index) : i64 + %172 = llvm.mul %52, %171 : i64 + %173 = llvm.add %172, %53 : i64 + %174 = llvm.getelementptr %170[%173] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %64, %174 : f32, !llvm.ptr + %175 = llvm.extractvalue %33[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %176 = llvm.mlir.constant(3 : index) : i64 + %177 = llvm.mul %53, %176 : i64 + %178 = llvm.add %177, %51 : i64 + %179 = llvm.getelementptr %175[%178] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %56, %179 : f32, !llvm.ptr + %180 = llvm.extractvalue %33[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %181 = llvm.mlir.constant(3 : index) : i64 + %182 = llvm.mul %53, %181 : i64 + %183 = llvm.add %182, %52 : i64 + %184 = llvm.getelementptr %180[%183] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %55, %184 : f32, !llvm.ptr + %185 = llvm.extractvalue %33[1] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + %186 = llvm.mlir.constant(3 : index) : i64 + %187 = llvm.mul %53, %186 : i64 + %188 = llvm.add %187, %53 : i64 + %189 = llvm.getelementptr %185[%188] : (!llvm.ptr, i64) -> !llvm.ptr, f32 + llvm.store %64, %189 : f32, !llvm.ptr + %190 = llvm.mlir.constant(1.500000e+00 : f32) : f32 + %191 = llvm.fptoui %190 : f32 to i32 + %192 = llvm.sext %191 : i32 to i64 + %193 = llvm.extractvalue %16[0] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + llvm.call @free(%193) : (!llvm.ptr) -> () + %194 = llvm.extractvalue %33[0] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + llvm.call @free(%194) : (!llvm.ptr) -> () + %195 = llvm.extractvalue %50[0] : !llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)> + llvm.call @free(%195) : (!llvm.ptr) -> () + llvm.return +} + diff --git a/mlir/test/conv2d/conv.c b/mlir/test/conv2d/conv.c new file mode 100644 index 000000000000..f2c34db4ce71 --- /dev/null +++ b/mlir/test/conv2d/conv.c @@ -0,0 +1,95 @@ +#include +#include + +#define INPUTSIZE 4 +#define KERNELSIZE 3 + +double example_in[INPUTSIZE][INPUTSIZE] = { + {1,2,3,4}, + {2,3,4,6}, + {4,3,2,1}, + {6,8,4,7} +}; + +double example_kernel[KERNELSIZE][KERNELSIZE] = { + {1,0,-1}, + {1,0,-1}, + {1,0,-1} +}; + +void conv2d(double **input, double** kernel, double** output) { + int delta = KERNELSIZE / 2; + + for(int x=0; x=0 && imgY>=0 && imgX, %B: memref<128x128xf32>, %C: memref<128x128xf32>) { + affine.for %i = 0 to 128 { + affine.for %j = 0 to 128 { + affine.for %k = 0 to 128 { + %a = affine.load %A[%i, %k] : memref<128x128xf32> + %b = affine.load %B[%k, %j] : memref<128x128xf32> + %c = affine.load %C[%i, %j] : memref<128x128xf32> + %mul = arith.mulf %a, %b : f32 + %add = arith.addf %mul, %c : f32 + affine.store %add, %C[%i, %j] : memref<128x128xf32> + + } + + } + + } + return + +} + diff --git a/mlir/test/conv2d/scftest.mlir b/mlir/test/conv2d/scftest.mlir new file mode 100644 index 000000000000..fbd25d1772ed --- /dev/null +++ b/mlir/test/conv2d/scftest.mlir @@ -0,0 +1,19 @@ +module { +func.func @main(%buffer: memref<1024xf32>, %lb: index, + %ub: index, %step: index) -> (f32) { + // Initial sum set to 0. + %sum_0 = arith.constant 0.0 : f32 + // iter_args binds initial values to the loop's region arguments. + %sum = scf.for %iv = %lb to %ub step %step + iter_args(%sum_iter = %sum_0) -> (f32) { + %t = memref.load %buffer[%iv] : memref<1024xf32> + %sum_next = arith.addf %sum_iter, %t : f32 + // Yield current iteration sum to next iteration %sum_iter or to %sum + // if final iteration. + scf.yield %sum_next : f32 + + } + return %sum : f32 + +} +} diff --git a/mlir/test/conv2d/tosa.conv2d.mlir b/mlir/test/conv2d/tosa.conv2d.mlir new file mode 100644 index 000000000000..32e34934030d --- /dev/null +++ b/mlir/test/conv2d/tosa.conv2d.mlir @@ -0,0 +1,14 @@ +module { + func.func @main() -> i32 { + %arg0 = arith.constant 10 : i32 + %arg1 = arith.constant 122: i32 + + %result = call @foo(%arg0, %arg1) : (i32, i32) -> i32 + return %result : i32 + } + + func.func @foo(%arg0: i32, %arg1: i32) -> i32 { + %0 = arith.addi %arg0, %arg1: i32 + return %0: i32 + } +} diff --git a/mlir/test/mlir-opt/example1.mlir b/mlir/test/mlir-opt/example1.mlir new file mode 100644 index 000000000000..a0e2a810794c --- /dev/null +++ b/mlir/test/mlir-opt/example1.mlir @@ -0,0 +1,21 @@ +#accesses = [ + affine_map<(m) -> (m)>, + affine_map<(m) -> (m)> +] + +#attrs = { + indexing_maps = #accesses, + iterator_types = ["parallel"] +} + +func.func @example(%a: memref>, %b: memref, strided<[2], offset: 1>>) { + linalg.generic #attrs + ins(%a: memref>) + outs(%b: memref, strided<[2], offset: 1>>) { + ^bb0(%aa: f32, %bb:vector<4xf32>): + %cc = "mk_compute"(%aa, %bb): (f32, vector<4xf32>) -> (vector<4xf32>) + linalg.yield %cc: vector<4xf32> + } + + return +} From f5d16806c6172ec66a006f82a820197a8abf46d8 Mon Sep 17 00:00:00 2001 From: HwisooSo Date: Fri, 18 Oct 2024 17:29:36 -0700 Subject: [PATCH 08/45] Added ShiftRight and Matmul (#12) --- matmul_test/dsp_matmul.py | 12 ++ .../dsp/SimpleBlocks/include/toy/Ops.td | 67 +++++++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 91 ++++++++++ .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 168 +++++++++++++++++- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 20 +++ .../Examples/DspExample/dsp_shiftRight_op.py | 8 + 6 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 matmul_test/dsp_matmul.py create mode 100644 mlir/test/Examples/DspExample/dsp_shiftRight_op.py diff --git a/matmul_test/dsp_matmul.py b/matmul_test/dsp_matmul.py new file mode 100644 index 000000000000..0c866fd2dc4c --- /dev/null +++ b/matmul_test/dsp_matmul.py @@ -0,0 +1,12 @@ +def main() { + var x = [[1.0, 2.0], [4.0, 5.0]]; + var y = [[1.0, 2.0], [4.0, 5.0]]; + var z = matmul(x, y); + print(z); + + + var x2 = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]; + var y2 = [[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]; + var z2 = matmul(x2, y2); + print(z2); +} diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 6aea97bdf926..d6c07a54c3bb 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -1989,6 +1989,70 @@ def FFT1DImgConjSymmOp : Dsp_Op<"fft1DimgConjSymm", let hasVerifier = 1; } + + +//===----------------------------------------------------------------------===// +// ShiftRightOp +//===----------------------------------------------------------------------===// + +def ShiftRightOp : Dsp_Op<"shiftRight", [Pure , DeclareOpInterfaceMethods]> { + let summary = "Bit-wise shift right a by b"; + let description = [{ + The shift right block shifts each element of a vector by right-hand side integer. + }]; + + let arguments = (ins F64Tensor:$lhs, F64Tensor:$rhs); //Problem: how can we do logical shift with floating point tensor? + let results = (outs F64Tensor); + + // Indicate that the operation has a custom parser and printer method. + // let hasCustomAssemblyFormat = 1; + // let assemblyFormat = [{ + // `(` $input `:` type($input1 , $input2) `)` attr-dict `to` type(results) + // }]; + // Allow building a MulOp with from the two input operands. + let builders = [ + OpBuilder<(ins "Value":$lhs, "Value":$rhs)> + ]; + + // Indicate that the operation has a custom parser and printer method. + // let hasCustomAssemblyFormat = 1; + + // let hasVerifier = 1; + } + + +//===----------------------------------------------------------------------===// +// MatmulOp +//===----------------------------------------------------------------------===// + +def MatmulOp : Dsp_Op<"matmul", [Pure , DeclareOpInterfaceMethods]> { + let summary = "Matrix multiplication a * b"; + let description = [{ + Matrix multiplication between the left-hand side and right-hand side. + }]; + + let arguments = (ins F64Tensor:$lhs, F64Tensor:$rhs); //Problem: how can we do logical shift with floating point tensor? + let results = (outs F64Tensor); + + // Indicate that the operation has a custom parser and printer method. + // let hasCustomAssemblyFormat = 1; + // let assemblyFormat = [{ + // `(` $input `:` type($input1 , $input2) `)` attr-dict `to` type(results) + // }]; + // Allow building a MulOp with from the two input operands. + let builders = [ + OpBuilder<(ins "Value":$lhs, "Value":$rhs)> + ]; + + // Indicate that the operation has a custom parser and printer method. + // let hasCustomAssemblyFormat = 1; + + let hasVerifier = 1; + } + + + + //===----------------------------------------------------------------------===// // Conv2DOp //===----------------------------------------------------------------------===// @@ -2017,3 +2081,6 @@ def Conv2DOp : Dsp_Op<"conv2d", } #endif // TOY_OPS + + + diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index eacf6a2e3421..533cbc51e833 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -628,6 +628,73 @@ void FFTImagOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, void FFTImagOp::inferShapes(){ getResult().setType(getLhs().getType()); } + + + + +//===----------------------------------------------------------------------===// + // MatmulOp + //===----------------------------------------------------------------------===// + + void MatmulOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs, mlir::Value rhs) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({lhs, rhs}); + } + + // mlir::ParseResult MatmulOp::parse(mlir::OpAsmParser &parser, + // mlir::OperationState &result) { + // return parseBinaryOp(parser, result); + // } + + // void MatmulOp::print(mlir::OpAsmPrinter &p) { printBinaryOp(p, *this); } + +mlir::LogicalResult MatmulOp::verify() { + + //auto resultType = llvm::dyn_cast(getResult().getType()); + + auto tensorLhs = getLhs().getType(); + auto shapeOfLhs = tensorLhs.getShape(); + + auto tensorRhs = getRhs().getType(); + auto shapeOfRhs = tensorRhs.getShape(); + + + if (shapeOfLhs[1] != shapeOfRhs[0]) + return emitOpError( + "Matmul: the second dimension of LHS should be equal to the first dimention of RHS."); + return mlir::success(); +} + + + /// Infer the output shape of the MatmulOp, this is required by the shape inference + /// interface. + void MatmulOp::inferShapes() { + + + //get the shape of Lhs & rhs + //add the shape for each dimension + // auto tensorInput = llvm::cast(getLhs().getType()); + auto tensorLhs = getLhs().getType(); + auto shapeOfLhs = tensorLhs.getShape(); + + auto tensorRhs = getRhs().getType(); + auto shapeOfRhs = tensorRhs.getShape(); + + std::vector shapeForOutput ; + + shapeForOutput.push_back(shapeOfLhs[0]); + shapeForOutput.push_back(shapeOfRhs[1]); + + + + mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, + getLhs().getType().getElementType()); + + //getResult().setType(getLhs().getType()); + getResult().setType(manipulatedType); +} + //===----------------------------------------------------------------------===// // zeroCrossCountOp //===----------------------------------------------------------------------===// @@ -2520,6 +2587,30 @@ mlir::LogicalResult FFT1DImgConjSymmOp::verify() { return mlir::success(); } + + +//===----------------------------------------------------------------------===// + // ShiftRightOp + //===----------------------------------------------------------------------===// + + void ShiftRightOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs, mlir::Value rhs) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({lhs, rhs}); + } + + // mlir::ParseResult SubOp::parse(mlir::OpAsmParser &parser, + // mlir::OperationState &result) { + // return parseBinaryOp(parser, result); + // } + + // void SubOp::print(mlir::OpAsmPrinter &p) { printBinaryOp(p, *this); } + + /// Infer the output shape of the ShiftRightOp, this is required by the shape inference + /// interface. + void ShiftRightOp::inferShapes() { getResult().setType(getLhs().getType()); } + + //===----------------------------------------------------------------------===// // Conv2DOp //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 4e4efe5de105..b15bb9d3a0df 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -6896,6 +6896,172 @@ struct BinaryOpLowering : public ConversionPattern { } }; + + +//===----------------------------------------------------------------------===// +// ToyToAffine AdditionalPatterns: Shift operations +//===----------------------------------------------------------------------===// + +struct ShiftRightOpLowering : public ConversionPattern { + ShiftRightOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::ShiftRightOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + + //Get the location of GainOp + auto loc = op->getLoc(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation + auto memRefType = convertTensorToMemRef(tensorType); + auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); + + + //first from 1 <= i < N + int64_t lb = 0 ; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + typename dsp::ShiftRightOp::Adaptor binaryAdaptor(operands); + + affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + auto ivY = forOpY.getInductionVar(); + rewriter.setInsertionPointToStart(forOpY.getBody()); + + auto loadedLhs = rewriter.create(loc, binaryAdaptor.getLhs(), ivY); + Value IntegerLhs = rewriter.create(loc, rewriter.getI64Type(), loadedLhs); + + auto loadedRhs = rewriter.create(loc, binaryAdaptor.getRhs(), ivY); + Value IntegerRhs = rewriter.create(loc, rewriter.getI64Type(), loadedRhs); + + auto LoweredOp = rewriter.create(loc, IntegerLhs, IntegerRhs); + + Value FloatOp = rewriter.create(loc, rewriter.getF64Type(), LoweredOp); + + rewriter.create(loc, FloatOp, alloc, ValueRange{ivY}); + + rewriter.setInsertionPointAfter(forOpY); + + + DEBUG_PRINT_NO_ARGS(); + + + //rewriter.replaceOp(op, FloatOp); + rewriter.replaceOp(op, alloc); + + return success(); + } +}; + + + + + +//===----------------------------------------------------------------------===// +// ToyToAffine AdditionalPatterns: Matmul operations +//===----------------------------------------------------------------------===// + +//template + + + +struct MatmulOpLowering : public ConversionPattern { + MatmulOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::MatmulOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + + //Get the location of GainOp + auto loc = op->getLoc(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + + // allocation & deallocation for the result of this operation + auto memRefType = convertTensorToMemRef(tensorType); + auto alloc_output = insertAllocAndDealloc(memRefType, loc, rewriter); + + typename dsp::MatmulOp::Adaptor binaryAdaptor(operands); + + auto lhsType = llvm::dyn_cast(op->getOperand(0).getType()); + //auto rhsType = llvm::dyn_cast(op->getOperand(1).getType()); + + //first from 1 <= i < N + int64_t lb = 0 ; + int64_t ub_0 = lhsType.getShape()[0]; + int64_t ub_1 = lhsType.getShape()[1]; + int64_t step = 1; + + + + Value constantZero = rewriter.create(loc, rewriter.getF64Type(), + rewriter.getF64FloatAttr(0)); + + + //NOTE: matrix [y, x] --> y means row, x means column + affine::AffineForOp forOpY = rewriter.create(loc, lb, ub_0, step); + auto ivY = forOpY.getInductionVar(); + rewriter.setInsertionPointToStart(forOpY.getBody()); + + affine::AffineForOp forOpX = rewriter.create(loc, lb, ub_1, step); + auto ivX = forOpX.getInductionVar(); + //auto getIterArg = forOpX.getBody()->getArgument(1); //HWISOO: Find this to check how previous codes did + rewriter.setInsertionPointToStart(forOpX.getBody()); + + rewriter.create(loc, constantZero, alloc_output, ValueRange{ivY,ivX}); + + affine::AffineForOp forOpIndex = rewriter.create(loc, lb, ub_1, step); + auto ivIndex = forOpIndex.getInductionVar(); + rewriter.setInsertionPointToStart(forOpIndex.getBody()); + + auto loadedLhs = rewriter.create(loc, binaryAdaptor.getLhs(), ValueRange{ivY,ivIndex}); + + auto loadedRhs = rewriter.create(loc, binaryAdaptor.getRhs(), ValueRange{ivIndex,ivX}); + + Value mulLhsRhs = rewriter.create(loc, loadedLhs, loadedRhs ); + + auto loadedResult = rewriter.create(loc, alloc_output, ValueRange{ivY, ivX}); + + Value addResultAndMul = rewriter.create(loc, loadedResult, mulLhsRhs); + + rewriter.create(loc, addResultAndMul, alloc_output, ValueRange{ivY,ivX}); + + /* + auto loadedLhs = rewriter.create(loc, binaryAdaptor.getLhs(), ivY); + Value IntegerLhs = rewriter.create(loc, rewriter.getI64Type(), loadedLhs); + + auto loadedRhs = rewriter.create(loc, binaryAdaptor.getRhs(), ivY); + Value IntegerRhs = rewriter.create(loc, rewriter.getI64Type(), loadedRhs); + + auto LoweredOp = rewriter.create(loc, IntegerLhs, IntegerRhs); + + Value FloatOp = rewriter.create(loc, rewriter.getF64Type(), LoweredOp); + + rewriter.create(loc, FloatOp, alloc, ValueRange{ivY}); + + */ + + rewriter.setInsertionPointAfter(forOpY); + + DEBUG_PRINT_NO_ARGS(); + + + //rewriter.replaceOp(op, FloatOp); + rewriter.replaceOp(op, alloc_output); + + return success(); + } +}; + + + //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: Unary operations //===----------------------------------------------------------------------===// @@ -7281,7 +7447,7 @@ void ToyToAffineLoweringPass::runOnOperation() { RunLenEncodingOpLowering, FIRFilterResSymmOptimizedOpLowering, LengthOpLowering, ReverseInputOpLowering, PaddingOpLowering, FIRFilterYSymmOptimizedOpLowering, FFT1DRealSymmOpLowering, - FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering>(&getContext()); + FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering, ShiftRightOpLowering, MatmulOpLowering>(&getContext()); // With the target and rewrite patterns defined, we can now attempt the // conversion. The conversion will signal failure if any of our `illegal` diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index c2f84bef1401..ff5645e9dc07 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -406,6 +406,26 @@ class MLIRGenImpl { } return builder.create(location, operands[0]); } + + // Shift right Op + if(callee == "shiftRight"){ + if(call.getArgs().size() != 2){ + emitError(location, "MLIR codegen encountered an error: dsp.shiftRight " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); + } + + // Matmul Op + if(callee == "matmul"){ + if(call.getArgs().size() != 2){ + emitError(location, "MLIR codegen encountered an error: dsp.matmul " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); + } if(callee == "zeroCrossCount"){ if(call.getArgs().size() != 1){ diff --git a/mlir/test/Examples/DspExample/dsp_shiftRight_op.py b/mlir/test/Examples/DspExample/dsp_shiftRight_op.py new file mode 100644 index 000000000000..420e75564691 --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_shiftRight_op.py @@ -0,0 +1,8 @@ +# RUN: toyc-ch2 %s -emit=mlir 2>&1 | FileCheck %s + +def main() { + var a = [50,50,50,50]; + var b = [2,3,4,5]; + var c = shiftRight(a, b); + print(c); +} From 73e00ebf8c405652267db1ad2142c5bfad00e462 Mon Sep 17 00:00:00 2001 From: Atharva Khedkar <55466743+AtharvaKhedkar@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:09:59 -0700 Subject: [PATCH 09/45] add convertbinary operation and underwatercomm app (#13) * add thresholdup, underwatercomm and add generate dtmf, --- .../Output/TryDSPApps/DTMFToneDetection.py | 43 + .../underWaterCommunication.py | 32 + .../TryDSPApps/underWaterCommunication.py | 32 + .../TryDSPApps/voiceActivityDetection.py | 19 + .../dsp/SimpleBlocks/include/toy/Ops.td | 43 + .../dsp/SimpleBlocks/mlir/Dialect.cpp | 303 +++--- .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 888 ++++++++++++------ .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 114 ++- 8 files changed, 1007 insertions(+), 467 deletions(-) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/DTMFToneDetection.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/underWaterCommunication.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/underWaterCommunication.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/voiceActivityDetection.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/DTMFToneDetection.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/DTMFToneDetection.py new file mode 100644 index 000000000000..c271fe777e77 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/DTMFToneDetection.py @@ -0,0 +1,43 @@ +def main() { + # GENERATE SIGNAL FOR '5' + var fs = 8000; + var duration = 0.5; + var f1 = 770; + var f2 = 1336; +# # var step = 1/fs; +# # print(step); +# # total instances = fs * duration +# var total_instances = fs * duration; +# var t = getRangeOfVector(0,4000,0.000125); +# var pi = 3.14159265359; +# var getMultiplier = 2 * pi * f1; +# var getSinDuration = gain(t, getMultiplier); +# var sig1 = sin(getSinDuration); + + +# var getMultiplier2 = 2 * pi * f2; +# var getSinDuration2 = gain(t, getMultiplier2); +# var sig2 = sin(getSinDuration2); +# var signal = sig1 + sig2; +# var finalsig = gain(signal, 0.5); + + + +# var noise = delay(signal, 5); +# var noisy_sig = signal + noise; +# var threshold = 4; + +# var fft_real = fft1dreal(noisy_sig); +# var fft_img = fft1dimg(noisy_sig); + +# var magnitude = square(fft_real) + square(fft_img); +# print(magnitude); +# # res = gain(sum , 1/N) +# var len1 = len(t); +# # var res = sum1 / len1; +# # print(sq_abs); +# var GetThresholdReal = threshold( magnitude , threshold); +var dtmf_sig = generateDtmf(5,duration,fs); +print(dtmf_sig); + +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/underWaterCommunication.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/underWaterCommunication.py new file mode 100644 index 000000000000..a9b85cd50939 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/underWaterCommunication.py @@ -0,0 +1,32 @@ +def main() { + var fs = 1000; + # var step = 1/fs; + # print(step); + var input = getRangeOfVector(0, 100000000, 1); + var pi = 3.14159265359; + var getMultiplier = 2 * pi * 5; + # print(getMultiplier); + var getSinDuration = gain(input, getMultiplier); + var signal = sin(getSinDuration ); + + var noise = delay(signal, 5); + var noisy_sig = signal + noise; + + + #design a low-pass filter : filterOrder = 5(odd) , cut-off freq=10 + # get wc = 2 * pi * cutoff_freq / fs + # get the filter response using filter(b,a, noisy_sig) + var fc = 1000; + # var Fs = 8000; + var wc = 2 * pi * 1000 / 500; #wc should vary from 0 to pi + var N = 5; + # var hid = sinc(wc, N); + var lpf = lowPassFIRFilter(wc, 1); #ideal low -pass filter + var lpf_w = lpf * hamming(N); + var FIRfilterResponse = FIRFilterResponse(noisy_sig, lpf_w); + + var threshold = 0.5; + var GetThresholdReal = thresholdUp(FIRfilterResponse, threshold, 0); + print(GetThresholdReal); + +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/underWaterCommunication.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/underWaterCommunication.py new file mode 100644 index 000000000000..e3290a755d24 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/underWaterCommunication.py @@ -0,0 +1,32 @@ +def main() { + var fs = 1000; + # var step = 1/fs; + # print(step); + var t = getRangeOfVector(0,1000,0.001); + var pi = 3.14159265359; + var getMultiplier = 2 * pi * 5; + # print(getMultiplier); + var getSinDuration = gain(t, getMultiplier); + var signal = sin(getSinDuration ); + + var noise = delay(signal, 5); + var noisy_sig = signal + noise; + + + #design a low-pass filter : filterOrder = 5(odd) , cut-off freq=10 + # get wc = 2 * pi * cutoff_freq / fs + # get the filter response using filter(b,a, noisy_sig) + var fc = 1000; + # var Fs = 8000; + var wc = 2 * pi * 1000 / 500; #wc should vary from 0 to pi + var N = 5; + # var hid = sinc(wc, N); + var lpf = lowPassFIRFilter(wc, 1); #ideal low -pass filter + var lpf_w = lpf * hamming(N); + var FIRfilterResponse = FIRFilterResponse(noisy_sig, lpf_w); + + var threshold = 0.5; + var GetThresholdReal = thresholdUp(FIRfilterResponse, threshold, 0); + print(GetThresholdReal); + +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/voiceActivityDetection.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/voiceActivityDetection.py new file mode 100644 index 000000000000..d1a5efb4fc12 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/voiceActivityDetection.py @@ -0,0 +1,19 @@ +def main() { + var fs = 1000; + # var step = 1/fs; + # print(step); + var t = getRangeOfVector(0,2000,0.001); + var pi = 3.14159265359; + var getMultiplier = 2 * pi * 5; + # print(getMultiplier); + var getSinDuration = gain(t, getMultiplier); + var signal = sin(getSinDuration ); + + var noise = delay(signal, 5); + var noisy_sig = signal + noise; + var threshold = 1.8; + var GetThresholdReal = threshold( noisy_sig , threshold); + var zcr = zeroCrossCount(GetThresholdReal); + print(GetThresholdReal); + print(zcr); +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index d6c07a54c3bb..5a5ffdcb0fe7 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -2080,6 +2080,49 @@ def Conv2DOp : Dsp_Op<"conv2d", let hasVerifier = 1; } +//===----------------------------------------------------------------------===// +// ThresholdUpOp +//===----------------------------------------------------------------------===// + +def ThresholdUpOp : Dsp_Op<"thresholdUp", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Converts all the values above threhold to 1 else 0"; + let description = [{ + Converts all the values above threhold to 1 if returnoriginal is false, returns original value if returnoriginal is true else 0 + }]; + + let arguments = (ins F64Tensor:$input, F64Tensor:$threshold, F64Tensor:$returnoriginal); + + let results = (outs F64Tensor:$output); + + let builders = [ + OpBuilder<(ins "Value":$input, "Value":$threshold, "Value":$returnoriginal)> + ]; + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// GenerateDTMFOp +//===----------------------------------------------------------------------===// + +def GenerateDTMFOp : Dsp_Op<"generateDtmf", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Generates signal for the character input"; + let description = [{ + Converts the characterr into signal + }]; + + let arguments = (ins F64Tensor:$input, F64Tensor:$duration, F64Tensor:$freq); + + let results = (outs F64Tensor:$output); + + let builders = [ + OpBuilder<(ins "Value":$input, "Value":$duration, "Value":$freq)> + ]; + let hasVerifier = 1; +} + + #endif // TOY_OPS diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index 533cbc51e833..5b45f98c85d5 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -247,7 +247,7 @@ mlir::LogicalResult ConstantOp::verify() { //===----------------------------------------------------------------------===// void ModuloOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs) { + mlir::Value lhs, mlir::Value rhs) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); state.addOperands({lhs, rhs}); } @@ -608,90 +608,81 @@ void SubOp::inferShapes() { getResult().setType(getLhs().getType()); } // FFTRealOp //===----------------------------------------------------------------------===// -void FFTRealOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs) { +void FFTRealOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs) { state.addTypes(lhs.getType()); state.addOperands({lhs}); } -void FFTRealOp::inferShapes(){ getResult().setType(getLhs().getType()); } +void FFTRealOp::inferShapes() { getResult().setType(getLhs().getType()); } //===----------------------------------------------------------------------===// // FFTImagOp //===----------------------------------------------------------------------===// -void FFTImagOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs) { +void FFTImagOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs) { state.addTypes(lhs.getType()); state.addOperands({lhs}); } -void FFTImagOp::inferShapes(){ getResult().setType(getLhs().getType()); } - - - - +void FFTImagOp::inferShapes() { getResult().setType(getLhs().getType()); } //===----------------------------------------------------------------------===// - // MatmulOp - //===----------------------------------------------------------------------===// +// MatmulOp +//===----------------------------------------------------------------------===// - void MatmulOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs) { - state.addTypes(UnrankedTensorType::get(builder.getF64Type())); - state.addOperands({lhs, rhs}); - } +void MatmulOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs, mlir::Value rhs) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({lhs, rhs}); +} - // mlir::ParseResult MatmulOp::parse(mlir::OpAsmParser &parser, - // mlir::OperationState &result) { - // return parseBinaryOp(parser, result); - // } +// mlir::ParseResult MatmulOp::parse(mlir::OpAsmParser &parser, +// mlir::OperationState &result) { +// return parseBinaryOp(parser, result); +// } - // void MatmulOp::print(mlir::OpAsmPrinter &p) { printBinaryOp(p, *this); } +// void MatmulOp::print(mlir::OpAsmPrinter &p) { printBinaryOp(p, *this); } mlir::LogicalResult MatmulOp::verify() { - //auto resultType = llvm::dyn_cast(getResult().getType()); + // auto resultType = + // llvm::dyn_cast(getResult().getType()); - auto tensorLhs = getLhs().getType(); + auto tensorLhs = getLhs().getType(); auto shapeOfLhs = tensorLhs.getShape(); auto tensorRhs = getRhs().getType(); auto shapeOfRhs = tensorRhs.getShape(); - - + if (shapeOfLhs[1] != shapeOfRhs[0]) - return emitOpError( - "Matmul: the second dimension of LHS should be equal to the first dimention of RHS."); + return emitOpError("Matmul: the second dimension of LHS should be equal to the first dimention of RHS."); return mlir::success(); } +/// Infer the output shape of the MatmulOp, this is required by the shape +/// inference interface. +void MatmulOp::inferShapes() { - /// Infer the output shape of the MatmulOp, this is required by the shape inference - /// interface. - void MatmulOp::inferShapes() { - - - //get the shape of Lhs & rhs - //add the shape for each dimension - // auto tensorInput = llvm::cast(getLhs().getType()); - auto tensorLhs = getLhs().getType(); + // get the shape of Lhs & rhs + // add the shape for each dimension + // auto tensorInput = llvm::cast(getLhs().getType()); + auto tensorLhs = getLhs().getType(); auto shapeOfLhs = tensorLhs.getShape(); auto tensorRhs = getRhs().getType(); auto shapeOfRhs = tensorRhs.getShape(); - - std::vector shapeForOutput ; + + std::vector shapeForOutput; shapeForOutput.push_back(shapeOfLhs[0]); shapeForOutput.push_back(shapeOfRhs[1]); - - - mlir::TensorType manipulatedType = mlir::RankedTensorType::get(shapeForOutput, - getLhs().getType().getElementType()); + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getLhs().getType().getElementType()); - //getResult().setType(getLhs().getType()); + // getResult().setType(getLhs().getType()); getResult().setType(manipulatedType); } @@ -699,8 +690,8 @@ mlir::LogicalResult MatmulOp::verify() { // zeroCrossCountOp //===----------------------------------------------------------------------===// -void zeroCrossCountOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs) { +void zeroCrossCountOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value lhs) { state.addTypes(UnrankedTensorType::get(builder.getF64Type())); // state.addTypes(builder.getF64Type())); // state.addTypes(builder.getI64Type()); @@ -2587,97 +2578,185 @@ mlir::LogicalResult FFT1DImgConjSymmOp::verify() { return mlir::success(); } - - //===----------------------------------------------------------------------===// - // ShiftRightOp - //===----------------------------------------------------------------------===// - - void ShiftRightOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs) { - state.addTypes(UnrankedTensorType::get(builder.getF64Type())); - state.addOperands({lhs, rhs}); - } +// ShiftRightOp +//===----------------------------------------------------------------------===// - // mlir::ParseResult SubOp::parse(mlir::OpAsmParser &parser, - // mlir::OperationState &result) { - // return parseBinaryOp(parser, result); - // } +void ShiftRightOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs, mlir::Value rhs) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({lhs, rhs}); +} - // void SubOp::print(mlir::OpAsmPrinter &p) { printBinaryOp(p, *this); } +// mlir::ParseResult SubOp::parse(mlir::OpAsmParser &parser, +// mlir::OperationState &result) { +// return parseBinaryOp(parser, result); +// } - /// Infer the output shape of the ShiftRightOp, this is required by the shape inference - /// interface. - void ShiftRightOp::inferShapes() { getResult().setType(getLhs().getType()); } +// void SubOp::print(mlir::OpAsmPrinter &p) { printBinaryOp(p, *this); } +// Infer the output shape of the ShiftRightOp, this is required by the shape inference. +// interface. +void ShiftRightOp::inferShapes() { getResult().setType(getLhs().getType()); } //===----------------------------------------------------------------------===// // Conv2DOp //===----------------------------------------------------------------------===// -void Conv2DOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value input, mlir::Value weight, mlir::Value bias) { - state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); - state.addOperands({input, weight, bias}); +void Conv2DOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value input, mlir::Value weight, mlir::Value bias) { + state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); + state.addOperands({input, weight, bias}); } void Conv2DOp::inferShapes() { - auto inputType = llvm::dyn_cast(getInput().getType()); - auto kernelType = llvm::dyn_cast(getKernel().getType()); + auto inputType = llvm::dyn_cast(getInput().getType()); + auto kernelType = llvm::dyn_cast(getKernel().getType()); - int64_t IH = inputType.getShape()[0]; - int64_t IW = inputType.getShape()[1]; - int64_t KH = kernelType.getShape()[0]; - int64_t KW = kernelType.getShape()[1]; - int64_t OH = IH-KH+1, OW=IW-KW+1; + int64_t IH = inputType.getShape()[0]; + int64_t IW = inputType.getShape()[1]; + int64_t KH = kernelType.getShape()[0]; + int64_t KW = kernelType.getShape()[1]; + int64_t OH = IH - KH + 1, OW = IW - KW + 1; - SmallVector dims = {OH, OW}; - getResult().setType(RankedTensorType::get(dims, inputType.getElementType())); + SmallVector dims = {OH, OW}; + getResult().setType(RankedTensorType::get(dims, inputType.getElementType())); } mlir::LogicalResult Conv2DOp::verify() { - auto inputType = llvm::dyn_cast(getInput().getType()); - auto kernelType = llvm::dyn_cast(getKernel().getType()); - auto biasType = llvm::dyn_cast(getBias().getType()); + auto inputType = llvm::dyn_cast(getInput().getType()); + auto kernelType = llvm::dyn_cast(getKernel().getType()); + auto biasType = llvm::dyn_cast(getBias().getType()); - if(!inputType) { - llvm::errs() << "expect a ranked tensor for input, get " << getInput(); - return mlir::failure(); - } - if(!kernelType) { - llvm::errs() << "expect a ranked tensor for kernel, get " << getKernel(); - return mlir::failure(); - } - if(!biasType) { - llvm::errs() << "expect a one dimensional ranked tensor for bias, get " << getBias(); - return mlir::failure(); - } + if (!inputType) { + llvm::errs() << "expect a ranked tensor for input, get " << getInput(); + return mlir::failure(); + } + if (!kernelType) { + llvm::errs() << "expect a ranked tensor for kernel, get " << getKernel(); + return mlir::failure(); + } + if (!biasType) { + llvm::errs() << "expect a one dimensional ranked tensor for bias, get " + << getBias(); + return mlir::failure(); + } - auto inputRank = inputType.getRank(); - auto kernelRank = kernelType.getRank(); + auto inputRank = inputType.getRank(); + auto kernelRank = kernelType.getRank(); - if(inputRank != 2 ) { - llvm::errs() << "expect 2 dimensional input, format N IH IW IC, get " << inputRank; - return mlir::failure(); - } - if(kernelRank != 2 ) { - llvm::errs() << "expect 2 dimensional kernel, format OC KH KW IC."; - return mlir::failure(); - } + if (inputRank != 2) { + llvm::errs() << "expect 2 dimensional input, format N IH IW IC, get " + << inputRank; + return mlir::failure(); + } + if (kernelRank != 2) { + llvm::errs() << "expect 2 dimensional kernel, format OC KH KW IC."; + return mlir::failure(); + } - if(inputType.getShape()[0] < kernelType.getShape()[0]) { - llvm::errs() << "input shape < kernel shape at 1st dimension"; - return mlir::failure(); - } + if (inputType.getShape()[0] < kernelType.getShape()[0]) { + llvm::errs() << "input shape < kernel shape at 1st dimension"; + return mlir::failure(); + } - if(inputType.getShape()[1] < kernelType.getShape()[1]) { - llvm::errs() << "input shape < kernel shape at 2nd dimension"; - return mlir::failure(); - } - + if (inputType.getShape()[1] < kernelType.getShape()[1]) { + llvm::errs() << "input shape < kernel shape at 2nd dimension"; + return mlir::failure(); + } + + return mlir::success(); +} + +//===----------------------------------------------------------------------===// +// ThresholdUpOp +//===----------------------------------------------------------------------===// + +mlir::LogicalResult ThresholdUpOp::verify() { + int64_t returnOriginal = 5; + Value returnoriginal = getOperand(2); + dsp::ConstantOp constantOp1stArg = + returnoriginal.getDefiningOp(); + DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); + auto elements = constantLhsValue.getValues(); + float LenN = elements[0].getValueAsDouble(); + returnOriginal = (int64_t)LenN; + + // filter-order even not supported -- so making it odd + if (returnOriginal != 0 && returnOriginal != 1) { + return mlir::failure(); + } + return mlir::success(); +} + +void ThresholdUpOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value input, mlir::Value threshold, + mlir::Value returnoriginal) { + state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); + state.addOperands({input, threshold, returnoriginal}); +} +void ThresholdUpOp::inferShapes() { getResult().setType(getInput().getType()); } + +//===----------------------------------------------------------------------===// +// GenerateDTMFOp +//===----------------------------------------------------------------------===// + +mlir::LogicalResult GenerateDTMFOp::verify() { + int64_t returnOriginal = 5; + Value returnoriginal = getOperand(0); + dsp::ConstantOp constantOp1stArg = + returnoriginal.getDefiningOp(); + DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); + auto elements = constantLhsValue.getValues(); + float LenN = elements[0].getValueAsDouble(); + returnOriginal = (int64_t)LenN; + + // DTMF created only for numbers 0-9 + if (returnOriginal != 0 && returnOriginal != 1 && returnOriginal != 2 && + returnOriginal != 3 && returnOriginal != 4 && returnOriginal != 5 && + returnOriginal != 6 && returnOriginal != 7 && returnOriginal != 8 && + returnOriginal != 9) { + return mlir::failure(); + } return mlir::success(); } +void GenerateDTMFOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value input, + mlir::Value duration, mlir::Value freq) { + state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); + state.addOperands({input, duration, freq}); +} +void GenerateDTMFOp::inferShapes() { + std::vector shapeForOutput; + double duration = 1; + int64_t freq = 1; + + // To extract value from the SSA value: + // get the Operand + // convert it to ConstantOp + // convert it to corresponding elements attribute + // extract the value as float then convert to int + Value durationval = getOperand(1); + dsp::ConstantOp constantOp1stArg = durationval.getDefiningOp(); + DenseElementsAttr constantLhsValue = constantOp1stArg.getValue(); + auto elements = constantLhsValue.getValues(); + duration = elements[0].getValueAsDouble(); + + Value freqval = getOperand(2); + dsp::ConstantOp constantOp2ndArg = freqval.getDefiningOp(); + DenseElementsAttr constantRhsValue = constantOp2ndArg.getValue(); + auto elements2 = constantRhsValue.getValues(); + float LenN2 = elements2[0].getValueAsDouble(); + freq = (int64_t)LenN2; +auto finalShape = freq * duration; + shapeForOutput.push_back(finalShape); + mlir::TensorType outputType = mlir::RankedTensorType::get( + shapeForOutput, getInput().getType().getElementType()); + + getResult().setType(outputType); +} + //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index b15bb9d3a0df..7ffc17f8566e 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -1800,18 +1800,18 @@ struct LengthOpLowering : public ConversionPattern { struct FFTRealOpLowering : public ConversionPattern { // constructor takes the mlir context and the operation as inputs - FFTRealOpLowering(MLIRContext *ctx) + FFTRealOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::FFTRealOp::getOperationName(), 1, ctx) {} // matchandrewrite - actual lowering logic of the operation LogicalResult // return type is logical --> success or failure // checks if the correct function is passed and rewrites it - // takes in the pointer to the operation, list of operands, and the rewriter object - // const - function doesn't modify the class it belongs to - // final - can't be overridden + // takes in the pointer to the operation, list of operands, and the rewriter + // object const - function doesn't modify the class it belongs to final - + // can't be overridden matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { - // get location of the operation + // get location of the operation auto loc = op->getLoc(); // get the type of the result auto tensorType = llvm::cast((*op->result_type_begin())); @@ -1821,7 +1821,6 @@ struct FFTRealOpLowering : public ConversionPattern { auto alloc_temp_real = insertAllocAndDealloc(memrefType, loc, rewriter); auto alloc_temp_imag = insertAllocAndDealloc(memrefType, loc, rewriter); - // storing the input in real and 0.0 in imag // adaptor to get operands @@ -1830,17 +1829,22 @@ struct FFTRealOpLowering : public ConversionPattern { // bounds of the affine loop auto lb = rewriter.create(loc, 0); - auto ub = rewriter.create(loc, tensorType.getShape()[0]); + auto ub = + rewriter.create(loc, tensorType.getShape()[0]); auto step = rewriter.create(loc, 1); // load real and imag auto load_temp = rewriter.create(loc, lb, ub, step); rewriter.setInsertionPointToStart(load_temp.getBody()); auto iv = load_temp.getInductionVar(); - auto inputValue = rewriter.create(loc, input, ValueRange{iv}); - auto constantZero = rewriter.create(loc, llvm::APFloat(0.0), rewriter.getF64Type()); - rewriter.create(loc, inputValue, alloc_temp_real, ValueRange{iv}); - rewriter.create(loc, constantZero, alloc_temp_imag, ValueRange{iv}); + auto inputValue = + rewriter.create(loc, input, ValueRange{iv}); + auto constantZero = rewriter.create( + loc, llvm::APFloat(0.0), rewriter.getF64Type()); + rewriter.create(loc, inputValue, alloc_temp_real, + ValueRange{iv}); + rewriter.create(loc, constantZero, alloc_temp_imag, + ValueRange{iv}); rewriter.setInsertionPointAfter(load_temp); // alloc memory for reversed and dealloc when not required @@ -1848,15 +1852,18 @@ struct FFTRealOpLowering : public ConversionPattern { auto alloc_reversed_imag = insertAllocAndDealloc(memrefType, loc, rewriter); // bit reversal constants - auto constant1 = rewriter.create(loc, rewriter.getI64IntegerAttr(1)); - auto constant2 = rewriter.create(loc, rewriter.getI64IntegerAttr(2)); + auto constant1 = + rewriter.create(loc, rewriter.getI64IntegerAttr(1)); + auto constant2 = + rewriter.create(loc, rewriter.getI64IntegerAttr(2)); // Bit reversal loop auto bitReversal = rewriter.create(loc, lb, ub, step); rewriter.setInsertionPointToStart(bitReversal.getBody()); auto i = bitReversal.getInductionVar(); // Convert index to i64 - auto i_val = rewriter.create(loc, rewriter.getI64Type(), i); + auto i_val = + rewriter.create(loc, rewriter.getI64Type(), i); // Bit reversal logic auto bit0 = rewriter.create(loc, i_val, constant1); auto i_val_shr1 = rewriter.create(loc, i_val, constant1); @@ -1868,13 +1875,18 @@ struct FFTRealOpLowering : public ConversionPattern { auto rev_temp = rewriter.create(loc, rev_bit0, rev_bit1); auto rev = rewriter.create(loc, rev_temp, bit2); // Convert back to index - auto reversed_i = rewriter.create(loc, rewriter.getIndexType(), rev); + auto reversed_i = + rewriter.create(loc, rewriter.getIndexType(), rev); // Load values from temp arrays - auto real_val = rewriter.create(loc, alloc_temp_real, ValueRange{i}); - auto imag_val = rewriter.create(loc, alloc_temp_imag, ValueRange{i}); + auto real_val = + rewriter.create(loc, alloc_temp_real, ValueRange{i}); + auto imag_val = + rewriter.create(loc, alloc_temp_imag, ValueRange{i}); // Store values in reversed arrays - rewriter.create(loc, real_val, alloc_reversed_real, ValueRange{reversed_i}); - rewriter.create(loc, imag_val, alloc_reversed_imag, ValueRange{reversed_i}); + rewriter.create(loc, real_val, alloc_reversed_real, + ValueRange{reversed_i}); + rewriter.create(loc, imag_val, alloc_reversed_imag, + ValueRange{reversed_i}); rewriter.setInsertionPointAfter(bitReversal); // Cooley-Tukey FFT implementation @@ -1883,14 +1895,18 @@ struct FFTRealOpLowering : public ConversionPattern { auto stagesValue = rewriter.create(loc, stages); // Constants for complex arithmetic - auto pi = rewriter.create(loc, llvm::APFloat(M_PI), rewriter.getF64Type()); - auto neg2 = rewriter.create(loc, llvm::APFloat(-2.0), rewriter.getF64Type()); + auto pi = rewriter.create(loc, llvm::APFloat(M_PI), + rewriter.getF64Type()); + auto neg2 = rewriter.create( + loc, llvm::APFloat(-2.0), rewriter.getF64Type()); auto fftLoop = rewriter.create(loc, lb, stagesValue, step); rewriter.setInsertionPointToStart(fftLoop.getBody()); auto stage = fftLoop.getInductionVar(); - auto half_size = rewriter.create(loc, rewriter.create(loc, 1), stage); - auto full_size = rewriter.create(loc, half_size, rewriter.create(loc, 1)); + auto half_size = rewriter.create( + loc, rewriter.create(loc, 1), stage); + auto full_size = rewriter.create( + loc, half_size, rewriter.create(loc, 1)); auto outerLoop = rewriter.create(loc, lb, ub, full_size); rewriter.setInsertionPointToStart(outerLoop.getBody()); @@ -1905,10 +1921,14 @@ struct FFTRealOpLowering : public ConversionPattern { auto odd_index = rewriter.create(loc, even_index, half_size); // Calculate twiddle factor - auto j_i64 = rewriter.create(loc, rewriter.getI64Type(), j); - auto j_f64 = rewriter.create(loc, rewriter.getF64Type(), j_i64); - auto full_size_i64 = rewriter.create(loc, rewriter.getI64Type(), full_size); - auto full_size_f64 = rewriter.create(loc, rewriter.getF64Type(), full_size_i64); + auto j_i64 = + rewriter.create(loc, rewriter.getI64Type(), j); + auto j_f64 = + rewriter.create(loc, rewriter.getF64Type(), j_i64); + auto full_size_i64 = rewriter.create( + loc, rewriter.getI64Type(), full_size); + auto full_size_f64 = rewriter.create( + loc, rewriter.getF64Type(), full_size_i64); auto angle_div = rewriter.create(loc, j_f64, full_size_f64); auto angle_mul = rewriter.create(loc, neg2, angle_div); auto angle_final = rewriter.create(loc, pi, angle_mul); @@ -1916,21 +1936,27 @@ struct FFTRealOpLowering : public ConversionPattern { auto sin = rewriter.create(loc, angle_final); // Load odd value - auto odd_real = rewriter.create(loc, alloc_reversed_real, ValueRange{odd_index}); - auto odd_imag = rewriter.create(loc, alloc_reversed_imag, ValueRange{odd_index}); + auto odd_real = rewriter.create(loc, alloc_reversed_real, + ValueRange{odd_index}); + auto odd_imag = rewriter.create(loc, alloc_reversed_imag, + ValueRange{odd_index}); // Multiply by twiddle factor auto odd_real_cos = rewriter.create(loc, odd_real, cos); auto odd_imag_sin = rewriter.create(loc, odd_imag, sin); - auto t_real = rewriter.create(loc, odd_real_cos, odd_imag_sin); + auto t_real = + rewriter.create(loc, odd_real_cos, odd_imag_sin); auto odd_real_sin = rewriter.create(loc, odd_real, sin); auto odd_imag_cos = rewriter.create(loc, odd_imag, cos); - auto t_imag = rewriter.create(loc, odd_real_sin, odd_imag_cos); + auto t_imag = + rewriter.create(loc, odd_real_sin, odd_imag_cos); // Load even value - auto even_real = rewriter.create(loc, alloc_reversed_real, ValueRange{even_index}); - auto even_imag = rewriter.create(loc, alloc_reversed_imag, ValueRange{even_index}); + auto even_real = rewriter.create(loc, alloc_reversed_real, + ValueRange{even_index}); + auto even_imag = rewriter.create(loc, alloc_reversed_imag, + ValueRange{even_index}); // Butterfly operation auto new_even_real = rewriter.create(loc, even_real, t_real); @@ -1939,10 +1965,14 @@ struct FFTRealOpLowering : public ConversionPattern { auto new_odd_imag = rewriter.create(loc, even_imag, t_imag); // Store results - rewriter.create(loc, new_even_real, alloc_reversed_real, ValueRange{even_index}); - rewriter.create(loc, new_even_imag, alloc_reversed_imag, ValueRange{even_index}); - rewriter.create(loc, new_odd_real, alloc_reversed_real, ValueRange{odd_index}); - rewriter.create(loc, new_odd_imag, alloc_reversed_imag, ValueRange{odd_index}); + rewriter.create(loc, new_even_real, alloc_reversed_real, + ValueRange{even_index}); + rewriter.create(loc, new_even_imag, alloc_reversed_imag, + ValueRange{even_index}); + rewriter.create(loc, new_odd_real, alloc_reversed_real, + ValueRange{odd_index}); + rewriter.create(loc, new_odd_imag, alloc_reversed_imag, + ValueRange{odd_index}); // replace the operation with the final value rewriter.replaceOp(op, alloc_reversed_real); @@ -1950,25 +1980,24 @@ struct FFTRealOpLowering : public ConversionPattern { } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: FFTImagOp operations //===----------------------------------------------------------------------===// struct FFTImagOpLowering : public ConversionPattern { // constructor takes the mlir context and the operation as inputs - FFTImagOpLowering(MLIRContext *ctx) + FFTImagOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::FFTImagOp::getOperationName(), 1, ctx) {} // matchandrewrite - actual lowering logic of the operation LogicalResult // return type is logical --> success or failure // checks if the correct function is passed and rewrites it - // takes in the pointer to the operation, list of operands, and the rewriter object - // const - function doesn't modify the class it belongs to - // final - can't be overridden + // takes in the pointer to the operation, list of operands, and the rewriter + // object const - function doesn't modify the class it belongs to final - + // can't be overridden matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { - // get location of the operation + // get location of the operation auto loc = op->getLoc(); // get the type of the result auto tensorType = llvm::cast((*op->result_type_begin())); @@ -1978,7 +2007,6 @@ struct FFTImagOpLowering : public ConversionPattern { auto alloc_temp_real = insertAllocAndDealloc(memrefType, loc, rewriter); auto alloc_temp_imag = insertAllocAndDealloc(memrefType, loc, rewriter); - // storing the input in real and 0.0 in imag // adaptor to get operands @@ -1987,17 +2015,22 @@ struct FFTImagOpLowering : public ConversionPattern { // bounds of the affine loop auto lb = rewriter.create(loc, 0); - auto ub = rewriter.create(loc, tensorType.getShape()[0]); + auto ub = + rewriter.create(loc, tensorType.getShape()[0]); auto step = rewriter.create(loc, 1); // load real and imag auto load_temp = rewriter.create(loc, lb, ub, step); rewriter.setInsertionPointToStart(load_temp.getBody()); auto iv = load_temp.getInductionVar(); - auto inputValue = rewriter.create(loc, input, ValueRange{iv}); - auto constantZero = rewriter.create(loc, llvm::APFloat(0.0), rewriter.getF64Type()); - rewriter.create(loc, inputValue, alloc_temp_real, ValueRange{iv}); - rewriter.create(loc, constantZero, alloc_temp_imag, ValueRange{iv}); + auto inputValue = + rewriter.create(loc, input, ValueRange{iv}); + auto constantZero = rewriter.create( + loc, llvm::APFloat(0.0), rewriter.getF64Type()); + rewriter.create(loc, inputValue, alloc_temp_real, + ValueRange{iv}); + rewriter.create(loc, constantZero, alloc_temp_imag, + ValueRange{iv}); rewriter.setInsertionPointAfter(load_temp); // alloc memory for reversed and dealloc when not required @@ -2005,15 +2038,18 @@ struct FFTImagOpLowering : public ConversionPattern { auto alloc_reversed_imag = insertAllocAndDealloc(memrefType, loc, rewriter); // bit reversal constants - auto constant1 = rewriter.create(loc, rewriter.getI64IntegerAttr(1)); - auto constant2 = rewriter.create(loc, rewriter.getI64IntegerAttr(2)); + auto constant1 = + rewriter.create(loc, rewriter.getI64IntegerAttr(1)); + auto constant2 = + rewriter.create(loc, rewriter.getI64IntegerAttr(2)); // Bit reversal loop auto bitReversal = rewriter.create(loc, lb, ub, step); rewriter.setInsertionPointToStart(bitReversal.getBody()); auto i = bitReversal.getInductionVar(); // Convert index to i64 - auto i_val = rewriter.create(loc, rewriter.getI64Type(), i); + auto i_val = + rewriter.create(loc, rewriter.getI64Type(), i); // Bit reversal logic auto bit0 = rewriter.create(loc, i_val, constant1); auto i_val_shr1 = rewriter.create(loc, i_val, constant1); @@ -2025,13 +2061,18 @@ struct FFTImagOpLowering : public ConversionPattern { auto rev_temp = rewriter.create(loc, rev_bit0, rev_bit1); auto rev = rewriter.create(loc, rev_temp, bit2); // Convert back to index - auto reversed_i = rewriter.create(loc, rewriter.getIndexType(), rev); + auto reversed_i = + rewriter.create(loc, rewriter.getIndexType(), rev); // Load values from temp arrays - auto real_val = rewriter.create(loc, alloc_temp_real, ValueRange{i}); - auto imag_val = rewriter.create(loc, alloc_temp_imag, ValueRange{i}); + auto real_val = + rewriter.create(loc, alloc_temp_real, ValueRange{i}); + auto imag_val = + rewriter.create(loc, alloc_temp_imag, ValueRange{i}); // Store values in reversed arrays - rewriter.create(loc, real_val, alloc_reversed_real, ValueRange{reversed_i}); - rewriter.create(loc, imag_val, alloc_reversed_imag, ValueRange{reversed_i}); + rewriter.create(loc, real_val, alloc_reversed_real, + ValueRange{reversed_i}); + rewriter.create(loc, imag_val, alloc_reversed_imag, + ValueRange{reversed_i}); rewriter.setInsertionPointAfter(bitReversal); // Cooley-Tukey FFT implementation @@ -2040,14 +2081,18 @@ struct FFTImagOpLowering : public ConversionPattern { auto stagesValue = rewriter.create(loc, stages); // Constants for complex arithmetic - auto pi = rewriter.create(loc, llvm::APFloat(M_PI), rewriter.getF64Type()); - auto neg2 = rewriter.create(loc, llvm::APFloat(-2.0), rewriter.getF64Type()); + auto pi = rewriter.create(loc, llvm::APFloat(M_PI), + rewriter.getF64Type()); + auto neg2 = rewriter.create( + loc, llvm::APFloat(-2.0), rewriter.getF64Type()); auto fftLoop = rewriter.create(loc, lb, stagesValue, step); rewriter.setInsertionPointToStart(fftLoop.getBody()); auto stage = fftLoop.getInductionVar(); - auto half_size = rewriter.create(loc, rewriter.create(loc, 1), stage); - auto full_size = rewriter.create(loc, half_size, rewriter.create(loc, 1)); + auto half_size = rewriter.create( + loc, rewriter.create(loc, 1), stage); + auto full_size = rewriter.create( + loc, half_size, rewriter.create(loc, 1)); auto outerLoop = rewriter.create(loc, lb, ub, full_size); rewriter.setInsertionPointToStart(outerLoop.getBody()); @@ -2062,10 +2107,14 @@ struct FFTImagOpLowering : public ConversionPattern { auto odd_index = rewriter.create(loc, even_index, half_size); // Calculate twiddle factor - auto j_i64 = rewriter.create(loc, rewriter.getI64Type(), j); - auto j_f64 = rewriter.create(loc, rewriter.getF64Type(), j_i64); - auto full_size_i64 = rewriter.create(loc, rewriter.getI64Type(), full_size); - auto full_size_f64 = rewriter.create(loc, rewriter.getF64Type(), full_size_i64); + auto j_i64 = + rewriter.create(loc, rewriter.getI64Type(), j); + auto j_f64 = + rewriter.create(loc, rewriter.getF64Type(), j_i64); + auto full_size_i64 = rewriter.create( + loc, rewriter.getI64Type(), full_size); + auto full_size_f64 = rewriter.create( + loc, rewriter.getF64Type(), full_size_i64); auto angle_div = rewriter.create(loc, j_f64, full_size_f64); auto angle_mul = rewriter.create(loc, neg2, angle_div); auto angle_final = rewriter.create(loc, pi, angle_mul); @@ -2073,21 +2122,27 @@ struct FFTImagOpLowering : public ConversionPattern { auto sin = rewriter.create(loc, angle_final); // Load odd value - auto odd_real = rewriter.create(loc, alloc_reversed_real, ValueRange{odd_index}); - auto odd_imag = rewriter.create(loc, alloc_reversed_imag, ValueRange{odd_index}); + auto odd_real = rewriter.create(loc, alloc_reversed_real, + ValueRange{odd_index}); + auto odd_imag = rewriter.create(loc, alloc_reversed_imag, + ValueRange{odd_index}); // Multiply by twiddle factor auto odd_real_cos = rewriter.create(loc, odd_real, cos); auto odd_imag_sin = rewriter.create(loc, odd_imag, sin); - auto t_real = rewriter.create(loc, odd_real_cos, odd_imag_sin); + auto t_real = + rewriter.create(loc, odd_real_cos, odd_imag_sin); auto odd_real_sin = rewriter.create(loc, odd_real, sin); auto odd_imag_cos = rewriter.create(loc, odd_imag, cos); - auto t_imag = rewriter.create(loc, odd_real_sin, odd_imag_cos); + auto t_imag = + rewriter.create(loc, odd_real_sin, odd_imag_cos); // Load even value - auto even_real = rewriter.create(loc, alloc_reversed_real, ValueRange{even_index}); - auto even_imag = rewriter.create(loc, alloc_reversed_imag, ValueRange{even_index}); + auto even_real = rewriter.create(loc, alloc_reversed_real, + ValueRange{even_index}); + auto even_imag = rewriter.create(loc, alloc_reversed_imag, + ValueRange{even_index}); // Butterfly operation auto new_even_real = rewriter.create(loc, even_real, t_real); @@ -2096,10 +2151,14 @@ struct FFTImagOpLowering : public ConversionPattern { auto new_odd_imag = rewriter.create(loc, even_imag, t_imag); // Store results - rewriter.create(loc, new_even_real, alloc_reversed_real, ValueRange{even_index}); - rewriter.create(loc, new_even_imag, alloc_reversed_imag, ValueRange{even_index}); - rewriter.create(loc, new_odd_real, alloc_reversed_real, ValueRange{odd_index}); - rewriter.create(loc, new_odd_imag, alloc_reversed_imag, ValueRange{odd_index}); + rewriter.create(loc, new_even_real, alloc_reversed_real, + ValueRange{even_index}); + rewriter.create(loc, new_even_imag, alloc_reversed_imag, + ValueRange{even_index}); + rewriter.create(loc, new_odd_real, alloc_reversed_real, + ValueRange{odd_index}); + rewriter.create(loc, new_odd_imag, alloc_reversed_imag, + ValueRange{odd_index}); // replace the operation with the final value rewriter.replaceOp(op, alloc_reversed_imag); @@ -6896,8 +6955,6 @@ struct BinaryOpLowering : public ConversionPattern { } }; - - //===----------------------------------------------------------------------===// // ToyToAffine AdditionalPatterns: Shift operations //===----------------------------------------------------------------------===// @@ -6909,65 +6966,63 @@ struct ShiftRightOpLowering : public ConversionPattern { LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { - - //Get the location of GainOp - auto loc = op->getLoc(); - + + // Get the location of GainOp + auto loc = op->getLoc(); + // output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); + auto tensorType = llvm::cast((*op->result_type_begin())); // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - - - //first from 1 <= i < N - int64_t lb = 0 ; - int64_t ub = tensorType.getShape()[0]; + + // first from 1 <= i < N + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; int64_t step = 1; - typename dsp::ShiftRightOp::Adaptor binaryAdaptor(operands); - - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub, step); + typename dsp::ShiftRightOp::Adaptor binaryAdaptor(operands); + + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - auto loadedLhs = rewriter.create(loc, binaryAdaptor.getLhs(), ivY); - Value IntegerLhs = rewriter.create(loc, rewriter.getI64Type(), loadedLhs); + auto loadedLhs = + rewriter.create(loc, binaryAdaptor.getLhs(), ivY); + Value IntegerLhs = + rewriter.create(loc, rewriter.getI64Type(), loadedLhs); - auto loadedRhs = rewriter.create(loc, binaryAdaptor.getRhs(), ivY); - Value IntegerRhs = rewriter.create(loc, rewriter.getI64Type(), loadedRhs); + auto loadedRhs = + rewriter.create(loc, binaryAdaptor.getRhs(), ivY); + Value IntegerRhs = + rewriter.create(loc, rewriter.getI64Type(), loadedRhs); - auto LoweredOp = rewriter.create(loc, IntegerLhs, IntegerRhs); - - Value FloatOp = rewriter.create(loc, rewriter.getF64Type(), LoweredOp); - - rewriter.create(loc, FloatOp, alloc, ValueRange{ivY}); + auto LoweredOp = + rewriter.create(loc, IntegerLhs, IntegerRhs); - rewriter.setInsertionPointAfter(forOpY); - - - DEBUG_PRINT_NO_ARGS(); - - - //rewriter.replaceOp(op, FloatOp); - rewriter.replaceOp(op, alloc); - - return success(); - } -}; + Value FloatOp = + rewriter.create(loc, rewriter.getF64Type(), LoweredOp); + rewriter.create(loc, FloatOp, alloc, ValueRange{ivY}); + rewriter.setInsertionPointAfter(forOpY); + DEBUG_PRINT_NO_ARGS(); + + // rewriter.replaceOp(op, FloatOp); + rewriter.replaceOp(op, alloc); + return success(); + } +}; //===----------------------------------------------------------------------===// // ToyToAffine AdditionalPatterns: Matmul operations //===----------------------------------------------------------------------===// -//template - - +// template struct MatmulOpLowering : public ConversionPattern { MatmulOpLowering(MLIRContext *ctx) @@ -6976,92 +7031,101 @@ struct MatmulOpLowering : public ConversionPattern { LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { - - //Get the location of GainOp - auto loc = op->getLoc(); - - // output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); + // Get the location of GainOp + auto loc = op->getLoc(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc_output = insertAllocAndDealloc(memRefType, loc, rewriter); - - typename dsp::MatmulOp::Adaptor binaryAdaptor(operands); - auto lhsType = llvm::dyn_cast(op->getOperand(0).getType()); - //auto rhsType = llvm::dyn_cast(op->getOperand(1).getType()); - - //first from 1 <= i < N - int64_t lb = 0 ; - int64_t ub_0 = lhsType.getShape()[0]; - int64_t ub_1 = lhsType.getShape()[1]; - int64_t step = 1; + typename dsp::MatmulOp::Adaptor binaryAdaptor(operands); + auto lhsType = + llvm::dyn_cast(op->getOperand(0).getType()); + // auto rhsType = + // llvm::dyn_cast(op->getOperand(1).getType()); + // first from 1 <= i < N + int64_t lb = 0; + int64_t ub_0 = lhsType.getShape()[0]; + int64_t ub_1 = lhsType.getShape()[1]; + int64_t step = 1; - Value constantZero = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); + Value constantZero = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - - //NOTE: matrix [y, x] --> y means row, x means column - affine::AffineForOp forOpY = rewriter.create(loc, lb, ub_0, step); + // NOTE: matrix [y, x] --> y means row, x means column + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub_0, step); auto ivY = forOpY.getInductionVar(); rewriter.setInsertionPointToStart(forOpY.getBody()); - - affine::AffineForOp forOpX = rewriter.create(loc, lb, ub_1, step); + + affine::AffineForOp forOpX = + rewriter.create(loc, lb, ub_1, step); auto ivX = forOpX.getInductionVar(); - //auto getIterArg = forOpX.getBody()->getArgument(1); //HWISOO: Find this to check how previous codes did - rewriter.setInsertionPointToStart(forOpX.getBody()); + // auto getIterArg = forOpX.getBody()->getArgument(1); //HWISOO: Find this + // to check how previous codes did + rewriter.setInsertionPointToStart(forOpX.getBody()); - rewriter.create(loc, constantZero, alloc_output, ValueRange{ivY,ivX}); + rewriter.create(loc, constantZero, alloc_output, + ValueRange{ivY, ivX}); - affine::AffineForOp forOpIndex = rewriter.create(loc, lb, ub_1, step); + affine::AffineForOp forOpIndex = + rewriter.create(loc, lb, ub_1, step); auto ivIndex = forOpIndex.getInductionVar(); rewriter.setInsertionPointToStart(forOpIndex.getBody()); - auto loadedLhs = rewriter.create(loc, binaryAdaptor.getLhs(), ValueRange{ivY,ivIndex}); + auto loadedLhs = rewriter.create( + loc, binaryAdaptor.getLhs(), ValueRange{ivY, ivIndex}); - auto loadedRhs = rewriter.create(loc, binaryAdaptor.getRhs(), ValueRange{ivIndex,ivX}); - - Value mulLhsRhs = rewriter.create(loc, loadedLhs, loadedRhs ); - - auto loadedResult = rewriter.create(loc, alloc_output, ValueRange{ivY, ivX}); + auto loadedRhs = rewriter.create( + loc, binaryAdaptor.getRhs(), ValueRange{ivIndex, ivX}); - Value addResultAndMul = rewriter.create(loc, loadedResult, mulLhsRhs); - - rewriter.create(loc, addResultAndMul, alloc_output, ValueRange{ivY,ivX}); - - /* - auto loadedLhs = rewriter.create(loc, binaryAdaptor.getLhs(), ivY); - Value IntegerLhs = rewriter.create(loc, rewriter.getI64Type(), loadedLhs); + Value mulLhsRhs = rewriter.create(loc, loadedLhs, loadedRhs); - auto loadedRhs = rewriter.create(loc, binaryAdaptor.getRhs(), ivY); - Value IntegerRhs = rewriter.create(loc, rewriter.getI64Type(), loadedRhs); + auto loadedResult = rewriter.create( + loc, alloc_output, ValueRange{ivY, ivX}); + + Value addResultAndMul = + rewriter.create(loc, loadedResult, mulLhsRhs); + + rewriter.create(loc, addResultAndMul, alloc_output, + ValueRange{ivY, ivX}); + + /* + auto loadedLhs = rewriter.create(loc, +binaryAdaptor.getLhs(), ivY); Value IntegerLhs = +rewriter.create(loc, rewriter.getI64Type(), loadedLhs); + +auto loadedRhs = rewriter.create(loc, +binaryAdaptor.getRhs(), ivY); Value IntegerRhs = +rewriter.create(loc, rewriter.getI64Type(), loadedRhs); + + auto LoweredOp = rewriter.create(loc, IntegerLhs, +IntegerRhs); + + Value FloatOp = rewriter.create(loc, rewriter.getF64Type(), +LoweredOp); + + rewriter.create(loc, FloatOp, alloc, ValueRange{ivY}); + + */ + + rewriter.setInsertionPointAfter(forOpY); + + DEBUG_PRINT_NO_ARGS(); + + // rewriter.replaceOp(op, FloatOp); + rewriter.replaceOp(op, alloc_output); - auto LoweredOp = rewriter.create(loc, IntegerLhs, IntegerRhs); - - Value FloatOp = rewriter.create(loc, rewriter.getF64Type(), LoweredOp); - - rewriter.create(loc, FloatOp, alloc, ValueRange{ivY}); - - */ - - rewriter.setInsertionPointAfter(forOpY); - - DEBUG_PRINT_NO_ARGS(); - - - //rewriter.replaceOp(op, FloatOp); - rewriter.replaceOp(op, alloc_output); - return success(); } }; - - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: Unary operations //===----------------------------------------------------------------------===// @@ -7277,107 +7341,304 @@ struct TransposeOpLowering : public ConversionPattern { //===----------------------------------------------------------------------===// struct Conv2DOpLowering : public ConversionPattern { - Conv2DOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::Conv2DOp::getOperationName(), 1, ctx) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - - auto loc = op->getLoc(); - // output mem alloc and dealloc - auto output = llvm::dyn_cast((*op->result_type_begin())); - auto outputMem = convertTensorToMemRef(output); - auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); - - Conv2DOpAdaptor conv2dAdaptor(operands); - Value input = conv2dAdaptor.getInput(); - Value kernel = conv2dAdaptor.getKernel(); - - // ranked tensor type - auto inputType = llvm::dyn_cast(op->getOperand(0).getType()); - auto kernelType = llvm::dyn_cast(op->getOperand(1).getType()); - - ArrayRef inputShape = inputType.getShape(); - ArrayRef kernelShape = kernelType.getShape(); - - // input layout - int64_t IH = inputShape[0]; - int64_t IW = inputShape[1]; - - // kernel layout - int64_t KH = kernelShape[0]; - int64_t KW = kernelShape[1]; - - // output layout - ArrayRef outputShape = output.getShape(); - int64_t OH = outputShape[0]; - int64_t OW = outputShape[1]; - - - AffineExpr d0, d1, d2, d3; // declare affine expression: i, j, p, q - bindDims(rewriter.getContext(), d0, d1, d2, d3); // bind affine expr d0, d1 to current input dimension i, j, p, q - - // input affine map - AffineMap inputMap = AffineMap::get(4, 0, ArrayRef{d0+d2, d1+d3}, rewriter.getContext()); - // kernel affine map - AffineMap kernelMap = AffineMap::get(4, 0, ArrayRef{d2, d3}, rewriter.getContext()); - - // loops - int64_t lb=0, step=1; - /* looping i*/ - AffineForOp forOpI = rewriter.create(loc, lb, OH, step); - rewriter.setInsertionPointToStart(forOpI.getBody()); - auto ivI = forOpI.getInductionVar(); - - /* looping j*/ - AffineForOp forOpJ = rewriter.create(loc, lb, OW, step); - rewriter.setInsertionPointToStart(forOpJ.getBody()); - auto ivJ = forOpJ.getInductionVar(); - - // initilize output val - Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - rewriter.create(loc, zeroVal, alloc, ValueRange{ivI, ivJ}); - - /* looping p*/ - AffineForOp forOpP = rewriter.create(loc, lb, KH, step); - rewriter.setInsertionPointToStart(forOpP.getBody()); - auto ivP = forOpP.getInductionVar(); - - /* looping q*/ - AffineForOp forOpQ = rewriter.create(loc, lb, KW, step); - rewriter.setInsertionPointToStart(forOpQ.getBody()); - auto ivQ = forOpQ.getInductionVar(); - - // input bound check - Value inputRow = rewriter.create(loc, inputMap.getSubMap(0), ValueRange{ivI, ivJ, ivP, ivQ}); - Value inputCol = rewriter.create(loc, inputMap.getSubMap(1), ValueRange{ivI, ivJ, ivP, ivQ}); - Value rowUB = rewriter.create(loc, arith::CmpIPredicate::slt, inputRow, rewriter.create(loc, IH)); - Value colUB = rewriter.create(loc, arith::CmpIPredicate::slt, inputCol, rewriter.create(loc, IW)); - Value bound = rewriter.create(loc, rowUB, colUB); - - // bound condition - rewriter.create(loc, bound, [&](OpBuilder &builder, Location loc) { - // load input - Value inputVal = builder.create(loc, input, inputMap,ValueRange{ivI, ivJ, ivP, ivQ}); - Value kernelVal = builder.create(loc, kernel, kernelMap, ValueRange{ivI, ivJ, ivP, ivQ}); - // mul - Value prod = builder.create(loc, inputVal, kernelVal); - Value outputVal = builder.create(loc, alloc, ValueRange{ivI, ivJ}); - Value sum = builder.create(loc, prod, outputVal); - - // store the computed output - builder.create(loc, sum, alloc, ValueRange{ivI, ivJ}); - - builder.create(loc); - }); - - rewriter.replaceOp(op, alloc); - - return success(); - } + Conv2DOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::Conv2DOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + + auto loc = op->getLoc(); + // output mem alloc and dealloc + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); + + Conv2DOpAdaptor conv2dAdaptor(operands); + Value input = conv2dAdaptor.getInput(); + Value kernel = conv2dAdaptor.getKernel(); + + // ranked tensor type + auto inputType = + llvm::dyn_cast(op->getOperand(0).getType()); + auto kernelType = + llvm::dyn_cast(op->getOperand(1).getType()); + + ArrayRef inputShape = inputType.getShape(); + ArrayRef kernelShape = kernelType.getShape(); + + // input layout + int64_t IH = inputShape[0]; + int64_t IW = inputShape[1]; + + // kernel layout + int64_t KH = kernelShape[0]; + int64_t KW = kernelShape[1]; + + // output layout + ArrayRef outputShape = output.getShape(); + int64_t OH = outputShape[0]; + int64_t OW = outputShape[1]; + + AffineExpr d0, d1, d2, d3; // declare affine expression: i, j, p, q + bindDims( + rewriter.getContext(), d0, d1, d2, + d3); // bind affine expr d0, d1 to current input dimension i, j, p, q + + // input affine map + AffineMap inputMap = AffineMap::get( + 4, 0, ArrayRef{d0 + d2, d1 + d3}, rewriter.getContext()); + // kernel affine map + AffineMap kernelMap = AffineMap::get(4, 0, ArrayRef{d2, d3}, + rewriter.getContext()); + + // loops + int64_t lb = 0, step = 1; + /* looping i*/ + AffineForOp forOpI = rewriter.create(loc, lb, OH, step); + rewriter.setInsertionPointToStart(forOpI.getBody()); + auto ivI = forOpI.getInductionVar(); + + /* looping j*/ + AffineForOp forOpJ = rewriter.create(loc, lb, OW, step); + rewriter.setInsertionPointToStart(forOpJ.getBody()); + auto ivJ = forOpJ.getInductionVar(); + + // initilize output val + Value zeroVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + rewriter.create(loc, zeroVal, alloc, ValueRange{ivI, ivJ}); + + /* looping p*/ + AffineForOp forOpP = rewriter.create(loc, lb, KH, step); + rewriter.setInsertionPointToStart(forOpP.getBody()); + auto ivP = forOpP.getInductionVar(); + + /* looping q*/ + AffineForOp forOpQ = rewriter.create(loc, lb, KW, step); + rewriter.setInsertionPointToStart(forOpQ.getBody()); + auto ivQ = forOpQ.getInductionVar(); + + // input bound check + Value inputRow = rewriter.create( + loc, inputMap.getSubMap(0), ValueRange{ivI, ivJ, ivP, ivQ}); + Value inputCol = rewriter.create( + loc, inputMap.getSubMap(1), ValueRange{ivI, ivJ, ivP, ivQ}); + Value rowUB = rewriter.create( + loc, arith::CmpIPredicate::slt, inputRow, + rewriter.create(loc, IH)); + Value colUB = rewriter.create( + loc, arith::CmpIPredicate::slt, inputCol, + rewriter.create(loc, IW)); + Value bound = rewriter.create(loc, rowUB, colUB); + + // bound condition + rewriter.create( + loc, bound, [&](OpBuilder &builder, Location loc) { + // load input + Value inputVal = builder.create( + loc, input, inputMap, ValueRange{ivI, ivJ, ivP, ivQ}); + Value kernelVal = builder.create( + loc, kernel, kernelMap, ValueRange{ivI, ivJ, ivP, ivQ}); + // mul + Value prod = builder.create(loc, inputVal, kernelVal); + Value outputVal = + builder.create(loc, alloc, ValueRange{ivI, ivJ}); + Value sum = builder.create(loc, prod, outputVal); + + // store the computed output + builder.create(loc, sum, alloc, ValueRange{ivI, ivJ}); + + builder.create(loc); + }); + + rewriter.replaceOp(op, alloc); + + return success(); + } }; // conv2d +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: ThresholdUpOpLowering operations +//===----------------------------------------------------------------------===// + +struct ThresholdUpOpLowering : public ConversionPattern { + ThresholdUpOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::ThresholdUpOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + // Pseudo-code: + // y[n] = 1 , if a[i] >= threshld + // = 0 , else + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation + auto memRefType = convertTensorToMemRef(tensorType); + + auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); + + Value constant0 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + + Value constant1 = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + + // y[n] = a[n] , if a[i] >= threshld + // loop from 0 to len + + // load from X, + ThresholdUpOpAdaptor thresholdUpAdaptor(operands); + + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + // for loop from 0 to len(Output) + affine::AffineForOp forOpY = + rewriter.create(loc, lb, ub, step); + auto ivY = forOpY.getInductionVar(); + rewriter.setInsertionPointToStart(forOpY.getBody()); + + Value inputX = + rewriter.create(loc, thresholdUpAdaptor.getInput(), ivY); + + // Load the threshold value from the memref + auto thresholdMemRef = thresholdUpAdaptor.getThreshold(); + auto returnOriginalMemRef = thresholdUpAdaptor.getReturnoriginal(); + auto threshold = + rewriter.create(loc, thresholdMemRef, ValueRange{}); + auto returnOriginal = + rewriter.create(loc, returnOriginalMemRef, ValueRange{}); + + // Compare a[i] >= threshold + auto cmp1 = rewriter.create(loc, arith::CmpFPredicate::OGE, + inputX, threshold); + // Compare if return original is true or false and return 1 or original + // value + auto cmpro = rewriter.create(loc, arith::CmpFPredicate::OEQ, + constant1, returnOriginal); + + // Use select to choose between inputX and 1 + auto selectreturn = + rewriter.create(loc, cmpro, inputX, constant1); + + // Use select to choose between 0 and selectreturn + auto selectOp = + rewriter.create(loc, cmp1, selectreturn, constant0); + + // Store the result + rewriter.create(loc, selectOp, alloc, ivY); + + rewriter.setInsertionPointAfter(forOpY); + // debug + // forOpY->dump(); + // affine.store %cst, %alloc_10[] : memref + // %0 = affine.load %alloc_11[4] : memref<10xf64> + // affine.store %0, %alloc[0] : memref<1xf64> + + rewriter.replaceOp(op, alloc); + + return success(); + } +}; + +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: GenerateDTMFOpLowering operations +//===----------------------------------------------------------------------===// + +struct GenerateDTMFOpLowering : public ConversionPattern { + GenerateDTMFOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::GenerateDTMFOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation + auto memRefType = convertTensorToMemRef(tensorType); + + auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); + + GenerateDTMFOpAdaptor generatedtmfAdaptor(operands); + std::vector> freqPairs = { + {697, 1209}, {697, 1336}, {697, 1477}, {770, 1209}, + {770, 1336}, {770, 1477}, {852, 1209}, {852, 1336}, + {852, 1477}, {941, 1209}, {941, 1336}, {941, 1477}}; + + + +// auto inputval = generatedtmfAdaptor.getInput(); + +// Value GetDigitInput = op->getOperand(0); +// dsp::ConstantOp inputvl = inputval.getDefiningOp(); +// DenseElementsAttr inputvalue = inputvl.getValue(); +// auto elements1 = inputvalue.getValues(); +// float input = elements1[0].getValueAsDouble(); + +// Value GetDurationOp = op->getOperand(1); +// dsp::ConstantOp constantOp2ndArg = +// GetDurationOp.getDefiningOp(); +// DenseElementsAttr constant2ndValue = constantOp2ndArg.getValue(); +// auto elements2 = constant2ndValue.getValues(); +// float duration = elements2[0].getValueAsDouble(); + +// Value GetFreqOp = op->getOperand(2); +// dsp::ConstantOp constantOp3rdArg = +// GetFreqOp.getDefiningOp(); +// DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue(); +// auto elements3 = constant3rdValue.getValues(); +// float freq = elements3[0].getValueAsDouble(); + + +// const std::vector &pair = freqPairs[input]; +// int64_t f1 = pair[0]; +// int64_t f2 = pair[1]; +// int64_t lb = 0; +// int64_t ub = tensorType.getShape()[0]; +// int64_t step = 1; + +// Value const2pi = rewriter.create( +// loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(6.28318530718)); + +// Value mulProd = rewriter.create(loc, getLhs, getRhs); +// auto yMemRefType = MemRefType::get({ub}, rewriter.getF64Type()); +// auto tAlloc = rewriter.create(loc, yMemRefType); + + + + +// // for loop from 0 to len(Output) +// affine::AffineForOp forOpY = +// rewriter.create(loc, lb, ub, step); +// auto ivY = forOpY.getInductionVar(); +// rewriter.setInsertionPointToStart(forOpY.getBody()); + +// Value inputX = +// rewriter.create(loc, thresholdUpAdaptor.getInput(), ivY); + + +// // Store the result +// rewriter.create(loc, selectOp, alloc, ivY); + +// rewriter.setInsertionPointAfter(forOpY); + + + +// rewriter.replaceOp(op, alloc); + + return success(); + } +}; + } // namespace //===----------------------------------------------------------------------===// @@ -7388,9 +7649,9 @@ struct Conv2DOpLowering : public ConversionPattern { /// computationally intensive (like matmul for example...) while keeping the /// rest of the code in the Toy dialect. namespace { - struct ToyToAffineLoweringPass - : public PassWrapper> { - MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ToyToAffineLoweringPass) +struct ToyToAffineLoweringPass + : public PassWrapper> { + MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ToyToAffineLoweringPass) void getDependentDialects(DialectRegistry ®istry) const override { registry @@ -7402,38 +7663,39 @@ namespace { } // namespace void ToyToAffineLoweringPass::runOnOperation() { - // The first thing to define is the conversion target. This will define the - // final target for this lowering. - ConversionTarget target(getContext()); - - // We define the specific operations, or dialects, that are legal targets for - // this lowering. In our case, we are lowering to a combination of the - // `Affine`, `Arith`, `Func`, and `MemRef` dialects. - target.addLegalDialect(); - - // We also define the Toy dialect as Illegal so that the conversion will fail - // if any of these operations are *not* converted. Given that we actually want - // a partial lowering, we explicitly mark the Toy operations that don't want - // to lower, `dsp.print`, as `legal`. `dsp.print` will still need its operands - // to be updated though (as we convert from TensorType to MemRefType), so we - // only treat it as `legal` if its operands are legal. - target.addIllegalDialect(); - target.addDynamicallyLegalOp([](dsp::PrintOp op) { - return llvm::none_of(op->getOperandTypes(), - [](Type type) { return llvm::isa(type); }); - }); + // The first thing to define is the conversion target. This will define the + // final target for this lowering. + ConversionTarget target(getContext()); + + // We define the specific operations, or dialects, that are legal targets for + // this lowering. In our case, we are lowering to a combination of the + // `Affine`, `Arith`, `Func`, and `MemRef` dialects. + target.addLegalDialect(); + + // We also define the Toy dialect as Illegal so that the conversion will fail + // if any of these operations are *not* converted. Given that we actually want + // a partial lowering, we explicitly mark the Toy operations that don't want + // to lower, `dsp.print`, as `legal`. `dsp.print` will still need its operands + // to be updated though (as we convert from TensorType to MemRefType), so we + // only treat it as `legal` if its operands are legal. + target.addIllegalDialect(); + target.addDynamicallyLegalOp([](dsp::PrintOp op) { + return llvm::none_of(op->getOperandTypes(), + [](Type type) { return llvm::isa(type); }); + }); // Now that the conversion target has been defined, we just need to provide // the set of patterns that will lower the Toy operations. RewritePatternSet patterns(&getContext()); patterns.add< - AddOpLowering, ModuloOpLowering, ConstantOpLowering, FuncOpLowering, MulOpLowering, - PrintOpLowering, ReturnOpLowering, TransposeOpLowering, DelayOpLowering, - GainOpLowering, SubOpLowering, FIRFilterResponseOpLowering, - SlidingWindowAvgOpLowering, DownSamplingOpLowering, UpSamplingOpLowering, + AddOpLowering, ModuloOpLowering, ConstantOpLowering, FuncOpLowering, + MulOpLowering, PrintOpLowering, ReturnOpLowering, TransposeOpLowering, + DelayOpLowering, GainOpLowering, SubOpLowering, + FIRFilterResponseOpLowering, SlidingWindowAvgOpLowering, + DownSamplingOpLowering, UpSamplingOpLowering, LowPassFilter1stOrderOpLowering, HighPassFilterOpLowering, FFT1DOpLowering, IFFT1DOpLowering, HammingWindowOpLowering, DCTOpLowering, filterOpLowering, DivOpLowering, BitwiseAndOpLowering, @@ -7447,20 +7709,22 @@ void ToyToAffineLoweringPass::runOnOperation() { RunLenEncodingOpLowering, FIRFilterResSymmOptimizedOpLowering, LengthOpLowering, ReverseInputOpLowering, PaddingOpLowering, FIRFilterYSymmOptimizedOpLowering, FFT1DRealSymmOpLowering, - FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering, ShiftRightOpLowering, MatmulOpLowering>(&getContext()); - - // With the target and rewrite patterns defined, we can now attempt the - // conversion. The conversion will signal failure if any of our `illegal` - // operations were not converted successfully. - if (failed( - applyPartialConversion(getOperation(), target, std::move(patterns)))) - signalPassFailure(); + FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, + Conv2DOpLowering, ShiftRightOpLowering, MatmulOpLowering, + ThresholdUpOpLowering>(&getContext()); + + // With the target and rewrite patterns defined, we can now attempt the + // conversion. The conversion will signal failure if any of our `illegal` + // operations were not converted successfully. + if (failed( + applyPartialConversion(getOperation(), target, std::move(patterns)))) + signalPassFailure(); } /// Create a pass for lowering operations in the `Affine` and `Std` dialects, /// for a subset of the Toy IR (e.g. matmul). std::unique_ptr mlir::dsp::createLowerToAffinePass() { - return std::make_unique(); + return std::make_unique(); } #pragma GCC diagnostic pop diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index ff5645e9dc07..8efe06b2fd35 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -380,10 +380,10 @@ class MLIRGenImpl { } // Modulo Op - if(callee == "modulo"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.modulo " - "accepts only 2 arguments"); + if (callee == "modulo") { + if (call.getArgs().size() != 2) { + emitError(location, "MLIR codegen encountered an error: dsp.modulo " + "accepts only 2 arguments"); return nullptr; } return builder.create(location, operands[0], operands[1]); @@ -391,8 +391,9 @@ class MLIRGenImpl { if (callee == "fftReal") { if (call.getArgs().size() != 1) { - emitError(location, "MLIR codegen encountered an error: dsp.zeroCrossCount " - "accepts only 1 arguments"); + emitError(location, + "MLIR codegen encountered an error: dsp.zeroCrossCount " + "accepts only 1 arguments"); return nullptr; } return builder.create(location, operands[0]); @@ -400,46 +401,49 @@ class MLIRGenImpl { if (callee == "fftImag") { if (call.getArgs().size() != 1) { - emitError(location, "MLIR codegen encountered an error: dsp.zeroCrossCount " - "accepts only 1 arguments"); + emitError(location, + "MLIR codegen encountered an error: dsp.zeroCrossCount " + "accepts only 1 arguments"); return nullptr; } return builder.create(location, operands[0]); } - + // Shift right Op - if(callee == "shiftRight"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.shiftRight " - "accepts only 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1]); - } - + if (callee == "shiftRight") { + if (call.getArgs().size() != 2) { + emitError(location, "MLIR codegen encountered an error: dsp.shiftRight " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); + } + // Matmul Op - if(callee == "matmul"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.matmul " - "accepts only 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1]); - } - - if(callee == "zeroCrossCount"){ - if(call.getArgs().size() != 1){ - emitError(location, "MLIR codegen encountered an error: dsp.zeroCrossCount " - "accepts only 1 arguments"); + if (callee == "matmul") { + if (call.getArgs().size() != 2) { + emitError(location, "MLIR codegen encountered an error: dsp.matmul " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); + } + + if (callee == "zeroCrossCount") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.zeroCrossCount " + "accepts only 1 arguments"); return nullptr; } return builder.create(location, operands[0]); } - if(callee == "FIRFilterResponse"){ - if(call.getArgs().size() != 2){ - emitError(location, "MLIR codegen encountered an error: dsp.FIRFilterResponse " - "accepts only 2 arguments"); + if (callee == "FIRFilterResponse") { + if (call.getArgs().size() != 2) { + emitError(location, + "MLIR codegen encountered an error: dsp.FIRFilterResponse " + "accepts only 2 arguments"); return nullptr; } return builder.create(location, operands[0], @@ -822,14 +826,38 @@ class MLIRGenImpl { return builder.create(location, operands[0]); } - if(callee == "conv2d") { - if(call.getArgs().size() != 3) { - emitError(location, "MLIR codegen encountered an error: dsp.Conv2DOp " - "accepts 3 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1], operands[2]); - } + if (callee == "conv2d") { + if (call.getArgs().size() != 3) { + emitError(location, "MLIR codegen encountered an error: dsp.Conv2DOp " + "accepts 3 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1], + operands[2]); + } + + if (callee == "thresholdUp") { + if (call.getArgs().size() != 3) { + emitError(location, + "MLIR codegen encountered an error: dsp.thresholdUp " + "accepts 3 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1], + operands[2]); + } + + if (callee == "generateDtmf") { + if (call.getArgs().size() != 3) { + emitError(location, + "MLIR codegen encountered an error: dsp.GenerateDTMFOp " + "accepts 3 argument"); + return nullptr; + } + return builder.create(location, operands[0], operands[1], + operands[2]); + } + // Builtin calls have their custom operation, meaning this is a // straightforward emission. // if(callee == "delay"){ From 48041856d486eb72f3c792c9b87b1af97a69863e Mon Sep 17 00:00:00 2001 From: Atharva Khedkar <55466743+AtharvaKhedkar@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:23:18 -0700 Subject: [PATCH 10/45] add benchmark applications and helper scripts (#14) --- .../BenchmarkTest/CCode/ResultScript.py | 145 +++++++++++++ .../BenchmarkTest/CCode/audioCompression.c | 144 +++++++++++++ .../BenchmarkTest/CCode/audioEqualizer.c | 196 ++++++++++++++++++ .../BenchmarkTest/CCode/echocancelling.c | 114 ++++++++++ .../BenchmarkTest/CCode/energyOfSignal.c | 81 ++++++++ .../BenchmarkTest/CCode/hearingAid.c | 120 +++++++++++ .../CCode/lowPassFIRFilterDesign.c | 75 +++++++ .../BenchmarkTest/CCode/lowPassFull1.c | 147 +++++++++++++ .../BenchmarkTest/CCode/noisecancelling.c | 117 +++++++++++ .../CCode/periodogram2Conv.c | 39 ++-- .../CCode/underWaterCommunication.c | 165 +++++++++++++++ .../BenchmarkTest/CCode/vibrationAnalysis.c | 174 ++++++++++++++++ .../CCode/voiceActivityDetection.c | 117 +++++++++++ .../CountLinesFile.py | 0 .../ExtractOpName.py | 4 +- .../HammingWindow.py | 0 .../LMSNoiseFilter.py | 0 .../PyDSL/audioCompression.py | 0 .../PyDSL/energyOfSignal.py | 0 .../PyDSL/lowPassFIRFilterDesign1.py | 0 .../PyDSL/lowPassFull1.py | 0 .../PyDSL/noisecancelling.py | 0 .../PyDSL/periodogram2Conv.py | 0 .../Quantization.py | 0 .../TryHearingAid copy.py | 0 .../TryHearingAid.py | 0 .../audioEqualizer.py | 0 .../bandPassfilter.py | 0 .../filterDesign.py | 0 .../hearingAid.py | 0 .../highPassfilter.py | 0 .../lmsNoiseCancelling.py | 0 .../lowPassFilterApp.py | 0 .../periodogramHelp.py | 0 .../periodogramHelp2.py | 0 .../CCode/audioCompression.c | 107 ---------- .../PythonCodeForTest/CCode/energyOfSignal.c | 75 ------- .../CCode/lowPassFIRFilterDesign.c | 53 ----- .../PythonCodeForTest/CCode/lowPassFull1.c | 110 ---------- .../PythonCodeForTest/CCode/noisecancelling.c | 91 -------- .../PythonCodeForTest/Output/NoOfLinesInC.txt | 6 - .../Output/NoOfLinesInPy.txt | 6 - .../PythonCodeForTest/Output/OpsNameDump.txt | 1 - .../PythonCodeForTest/PythonCodeRough.py | 13 -- .../PythonCodeForTest/tokenCount.py | 139 ------------- .../Results/TryResultScript/EnergyOfSignal.py | 6 +- .../Results/TryResultScript/ScriptForCases.py | 132 +++++++----- .../TryResultScript/audioCompression.py | 2 +- .../Results/TryResultScript/audioEqualizer.py | 8 +- .../TryDSPApps/Results/TryResultScript/dsp1 | Bin 1320360 -> 0 bytes .../Results/TryResultScript/dsp1_Debug | Bin 1351496 -> 0 bytes .../TryResultScript}/echocancelling.py | 4 +- .../{hearing_aid.py => hearingAid.py} | 2 +- .../TryResultScript/lowPassFIRFilterDesign.py | 2 +- .../Results/TryResultScript/lowPassFull.py | 2 +- .../TryResultScript/noisecancelling.py | 2 +- .../TryResultScript/periodogram2Conv1.py | 2 +- .../TryResultScript}/vibrationAnalysis.py | 10 +- .../voiceActivityDetection.py | 6 +- .../TryDSPApps/underWaterCommunication.py | 32 --- 60 files changed, 1721 insertions(+), 728 deletions(-) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/ResultScript.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/audioCompression.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/audioEqualizer.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/echocancelling.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/energyOfSignal.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/hearingAid.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/lowPassFIRFilterDesign.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/lowPassFull1.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/noisecancelling.c rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/CCode/periodogram2Conv.c (62%) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/underWaterCommunication.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/vibrationAnalysis.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/voiceActivityDetection.c rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/CountLinesFile.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/ExtractOpName.py (90%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/HammingWindow.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/LMSNoiseFilter.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/PyDSL/audioCompression.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/PyDSL/energyOfSignal.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/PyDSL/lowPassFIRFilterDesign1.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/PyDSL/lowPassFull1.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/PyDSL/noisecancelling.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/PyDSL/periodogram2Conv.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/Quantization.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/TryHearingAid copy.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/TryHearingAid.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/audioEqualizer.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/bandPassfilter.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/filterDesign.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/hearingAid.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/highPassfilter.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/lmsNoiseCancelling.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/lowPassFilterApp.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/periodogramHelp.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{PythonCodeForTest => BenchmarkTest}/periodogramHelp2.py (100%) delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/audioCompression.c delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/energyOfSignal.c delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/lowPassFIRFilterDesign.c delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/lowPassFull1.c delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/noisecancelling.c delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Output/NoOfLinesInC.txt delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Output/NoOfLinesInPy.txt delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Output/OpsNameDump.txt delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PythonCodeRough.py delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/tokenCount.py delete mode 100755 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/dsp1 delete mode 100755 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/dsp1_Debug rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{ => Results/TryResultScript}/echocancelling.py (84%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/{hearing_aid.py => hearingAid.py} (93%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{ => Results/TryResultScript}/vibrationAnalysis.py (78%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{ => Results/TryResultScript}/voiceActivityDetection.py (76%) delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/underWaterCommunication.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/ResultScript.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/ResultScript.py new file mode 100644 index 000000000000..6eeb2301b2df --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/ResultScript.py @@ -0,0 +1,145 @@ +import os +import subprocess +import time + +# The script does the following +# Input : filename.py +# Output : TimeOfExecution for different IP sizes : +# Steps to run: +# Open a terminal at the path of the script -- +# Run: python ScriptForCases.py #3.11 validated + +# Pseudo-code: +# Iterate for all the input-size & update the input value in file +# Update logic -- change the 2nd parameter of line: var c = getRangeOfVector(init , Count, StepSize) +# Run the respective commands on the file + +# Path to the input file +# Apps = "lowPassFIRFilterDesign.c", "noisecancelling.c" , "echocancelling.c", "hearingAid.c", "audioEqualizer.c", "vibrationAnalysis.c", "underWaterCommunication.c", "voiceActivityDetection.c" +input_file_path = "voiceActivityDetection.c" +BasePathForLLVM = "DSP_MLIR" +OutputScriptPath = ( + "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/" +) +# OutputPath = BasePathForLLVM + "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/Output/" +print(f"Running Application {input_file_path}") +# Construct full output path +OutputPath = os.path.join(BasePathForLLVM, OutputScriptPath, "Output") + +# Check if the Output folder exists, create it if it doesn't +if not os.path.exists(OutputPath): + os.makedirs(OutputPath) + +# Now OutputPath is ready for use +print("InputPath:{}".format(BasePathForLLVM)) +print(f"OutputPath: {OutputPath}") +# exit() + +# ************ Don't change unless u required +# Define the values dictionary +inputValues = { + "10": 10, + "100": 100, + "1K": 1000, + "10K": 10000, + "20K": 20000, + "30K": 30000, + "40K": 40000, + "50K": 50000, + "100K": 100000, + "1M": 1000000, + "10M": 10000000, + "20M": 20000000, + "30M": 30000000, + "40M": 40000000, + "50M": 50000000, + "100M": 100000000, + # "1B": 1000000000 +} +NoOfIterations = 3 + + +# Define the cases +cases = [ + { + "gcc": True, + "clang": False, + "exe": "fileGCCOptExe", + }, + { + "clang": True, + "gcc": False, + "exe": "fileClangOptExe", + }, +] + + +with open(input_file_path, "r") as file: + lines = file.readlines() + +print("", end="\t") + +for case in cases: + print(f"{case['exe']}", end="\t") + +for key, value in inputValues.items(): + # Update the specific line in the file + # print("Updating for {}".format(value)) + print("\n{}".format(key), end="\t") + with open(input_file_path, "w") as file: + for line in lines: + if line.strip().startswith("#define INPUT_LENGTH"): + updated_line = f"#define INPUT_LENGTH {value}\n" + file.write(updated_line) + else: + file.write(line) + + for case in cases: + + if case["gcc"]: + command = f"gcc -O3 -o {OutputPath}/{case['exe']} {input_file_path} -lm", + if case["clang"]: + command = f"clang-17 -O3 {input_file_path} -o {OutputPath}/{case['exe']} -lm", + + result = subprocess.run(command, shell=True, capture_output=True, text=True) + + sum_exe_time = 0 + for i in range(0, NoOfIterations): + try: + process = subprocess.run( + "sudo sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches'", + shell=True, + check=True, + ) + # process.wait() + except subprocess.CalledProcessError as exc: + print(exc) + process.terminate() + # The command to be executed + + command2 = f"taskset -c 0 ./Output/{case['exe']}" + + # Record the start time + start_time = time.time() + + # Execute the command + try: + subprocess.run( + command2, + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + ) + # subprocess.run(command2, shell=True) + except subprocess.CalledProcessError as exc: + print( + f"Process failed because did not return a successful return code. " + f"Returned {exc.returncode}\n{exc}" + ) + + end_time = time.time() + execution_time = end_time - start_time + sum_exe_time = sum_exe_time + execution_time + avg_exe_time = sum_exe_time / NoOfIterations + print("{}".format(avg_exe_time), end="\t") diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/audioCompression.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/audioCompression.c new file mode 100644 index 000000000000..63418c10401a --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/audioCompression.c @@ -0,0 +1,144 @@ +#include +#include +#include +#include + +#define INPUT_LENGTH 40000 +#define NLEVELS 16 +#define MIN 0.0 +#define MAX 8.0 +#define THRESHOLD_VAL 4.0 + +double* getRangeOfVector(double start, int noOfSamples, double increment) { + double* output = malloc(noOfSamples * sizeof(double)); + if (!output) { + perror("Memory allocation failed in getRangeOfVector"); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < noOfSamples; i++) { + output[i] = start + i * increment; + } + + return output; +} + +void dft(double complex* output, const double* input, int length) { + for (int k = 0; k < length; k++) { + output[k] = 0; + for (int n = 0; n < length; n++) { + double angle = 2 * M_PI * k * n / length; + output[k] += input[n] * cexp(-I * angle); + } + } +} + +void threshold(double* output, const double* input, double thresh, int length) { + for (int i = 0; i < length; i++) { + output[i] = (fabs(input[i]) >= thresh) ? input[i] : 0; + } +} + +void quantization(double* output, const double* input, int nlevels, double max, double min, int length) { + double step = (max - min) / nlevels; + for (int i = 0; i < length; i++) { + output[i] = round((input[i] - min) / step) * step + min; + } +} + +int* runLenEncoding(const double* input, int length, int* rleLength) { + int* rle = malloc(2 * length * sizeof(int)); + if (!rle) { + perror("Memory allocation failed"); + exit(EXIT_FAILURE); + } + + int index = 0; + int count = 1; + for (int i = 1; i < length; i++) { + if (input[i] != input[i - 1]) { + rle[index++] = input[i - 1]; + rle[index++] = count; + count = 1; + } else { + count++; + } + } + rle[index++] = input[length - 1]; + rle[index++] = count; + + *rleLength = index; + return rle; +} + +double getElemAtIndx(const int* rle, int indx) { + return rle[indx]; +} + +int main() { + double* input = getRangeOfVector(0, INPUT_LENGTH, 1); + + double complex* fft = malloc(INPUT_LENGTH * sizeof(double complex)); + if (!fft) { + perror("Memory allocation failed"); + free(input); + return EXIT_FAILURE; + } + + dft(fft, input, INPUT_LENGTH); + + double* GetThresholdReal = malloc(INPUT_LENGTH * sizeof(double)); + double* GetThresholdImg = malloc(INPUT_LENGTH * sizeof(double)); + if (!GetThresholdReal || !GetThresholdImg) { + perror("Memory allocation failed"); + free(input); + free(fft); + free(GetThresholdReal); + free(GetThresholdImg); + return EXIT_FAILURE; + } + + for (int i = 0; i < INPUT_LENGTH; i++) { + GetThresholdReal[i] = creal(fft[i]); + GetThresholdImg[i] = cimag(fft[i]); + } + + threshold(GetThresholdReal, GetThresholdReal, THRESHOLD_VAL, INPUT_LENGTH); + threshold(GetThresholdImg, GetThresholdImg, THRESHOLD_VAL, INPUT_LENGTH); + + double* QuantOutReal = malloc(INPUT_LENGTH * sizeof(double)); + double* QuantOutImg = malloc(INPUT_LENGTH * sizeof(double)); + if (!QuantOutReal || !QuantOutImg) { + perror("Memory allocation failed"); + free(input); + free(fft); + free(GetThresholdReal); + free(GetThresholdImg); + free(QuantOutReal); + free(QuantOutImg); + return EXIT_FAILURE; + } + + quantization(QuantOutReal, GetThresholdReal, NLEVELS, MAX, MIN, INPUT_LENGTH); + quantization(QuantOutImg, GetThresholdImg, NLEVELS, MAX, MIN, INPUT_LENGTH); + + int rleLengthReal, rleLengthImg; + int* rLEOutReal = runLenEncoding(QuantOutReal, INPUT_LENGTH, &rleLengthReal); + int* rLEOutImg = runLenEncoding(QuantOutImg, INPUT_LENGTH, &rleLengthImg); + + double final1 = getElemAtIndx(rLEOutReal, 6); + double final2 = getElemAtIndx(rLEOutImg, 7); + printf("%f\n", final1); + printf("%f\n", final2); + + free(input); + free(fft); + free(GetThresholdReal); + free(GetThresholdImg); + free(QuantOutReal); + free(QuantOutImg); + free(rLEOutReal); + free(rLEOutImg); + + return 0; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/audioEqualizer.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/audioEqualizer.c new file mode 100644 index 000000000000..205f1806f8a2 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/audioEqualizer.c @@ -0,0 +1,196 @@ +#include +#include +#include + +#define PI 3.14159265359 +#define INPUT_LENGTH 100000000 + +// Function prototypes +double* getRangeOfVector(double start, int length, double increment); +double* lowPassFIRFilter(double wc, int length); +double* highPassFIRFilter(double wc, int length); +double* hamming(int length); +void elementWiseMultiplication(double* output, const double* array1, const double* array2, int length); +void FIRFilterResponse(double* output, const double* input, const double* filter, int inputLength, int filterLength); +void gain(double* output, const double* input, double gainFactor, int length); +void add(double* output, const double* input1, const double* input2, int length); +void sub(double* output, const double* input1, const double* input2, int length); + +// Implement the provided functions +double* hamming(int length) { + double* window = malloc(length * sizeof(double)); + if (!window) { + perror("Memory allocation failed in hamming"); + exit(EXIT_FAILURE); + } + for (int i = 0; i < length; i++) { + window[i] = 0.54 - 0.46 * cos(2 * PI * i / (length - 1)); + } + return window; +} + +double* lowPassFIRFilter(double wc, int length) { + double* filter = malloc(length * sizeof(double)); + if (!filter) { + perror("Memory allocation failed in lowPassFIRFilter"); + exit(EXIT_FAILURE); + } + + int mid = (length - 1) / 2; + for (int n = 0; n < length; n++) { + if (n == mid) { + filter[n] = wc / PI; + } else { + filter[n] = sin(wc * (n - mid)) / (PI * (n - mid)); + } + } + return filter; +} + +void elementWiseMultiplication(double* output, const double* array1, const double* array2, int length) { + for (int i = 0; i < length; i++) { + output[i] = array1[i] * array2[i]; + } +} + +// Implement additional required functions +double* getRangeOfVector(double start, int length, double increment) { + double* vector = malloc(length * sizeof(double)); + if (!vector) { + perror("Memory allocation failed in getRangeOfVector"); + exit(EXIT_FAILURE); + } + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; + } + return vector; +} + +double* highPassFIRFilter(double wc, int length) { + double* lpf = lowPassFIRFilter(wc, length); + double* hpf = malloc(length * sizeof(double)); + if (!hpf) { + perror("Memory allocation failed in highPassFIRFilter"); + exit(EXIT_FAILURE); + } + for (int i = 0; i < length; i++) { + hpf[i] = -lpf[i]; + } + int mid = (length - 1) / 2; + hpf[mid] += 1.0; + free(lpf); + return hpf; +} + +void FIRFilterResponse(double* output, const double* input, const double* filter, int inputLength, int filterLength) { + for (int i = 0; i < inputLength; i++) { + output[i] = 0; + for (int j = 0; j < filterLength; j++) { + if (i - j >= 0) { + output[i] += input[i - j] * filter[j]; + } + } + } +} + +void gain(double* output, const double* input, double gainFactor, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * gainFactor; + } +} + +void add(double* output, const double* input1, const double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] + input2[i]; + } +} + +void sub(double* output, const double* input1, const double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] - input2[i]; + } +} + +int main() { + double* input = getRangeOfVector(0, INPUT_LENGTH, 1); + double pi = PI; + double fc = 300; + double Fs = 8000; + double gainForBass = 2; + double gainForMid = 1.5; + double gainForTreble = 0.8; + + double wc = 2 * pi * fc / Fs; + int N = 101; + + // Low-pass filter + double* lpf = lowPassFIRFilter(wc, N); + double* hamming_window = hamming(N); + double* lpf_w = malloc(N * sizeof(double)); + elementWiseMultiplication(lpf_w, lpf, hamming_window, N); + + double* FIRfilterResponseForLpf = malloc(INPUT_LENGTH * sizeof(double)); + FIRFilterResponse(FIRfilterResponseForLpf, input, lpf_w, INPUT_LENGTH, N); + + double* gainWithLpf = malloc(INPUT_LENGTH * sizeof(double)); + gain(gainWithLpf, FIRfilterResponseForLpf, gainForBass, INPUT_LENGTH); + + // High-pass filter + double fc2 = 1500; + double wc2 = 2 * pi * fc2 / Fs; + double* hpf = highPassFIRFilter(wc2, N); + double* hpf_w = malloc(N * sizeof(double)); + elementWiseMultiplication(hpf_w, hpf, hamming_window, N); + + double* FIRfilterResponseForHpf = malloc(INPUT_LENGTH * sizeof(double)); + FIRFilterResponse(FIRfilterResponseForHpf, input, hpf_w, INPUT_LENGTH, N); + + double* gainWithHpf = malloc(INPUT_LENGTH * sizeof(double)); + gain(gainWithHpf, FIRfilterResponseForHpf, gainForTreble, INPUT_LENGTH); + + // Band-pass filter + double* lpf2 = lowPassFIRFilter(wc2, N); + double* lpf2_w = malloc(N * sizeof(double)); + elementWiseMultiplication(lpf2_w, lpf2, hamming_window, N); + + double* bpf_w = malloc(N * sizeof(double)); + sub(bpf_w, lpf2_w, lpf_w, N); + + double* FIRfilterResponseForBpf = malloc(INPUT_LENGTH * sizeof(double)); + FIRFilterResponse(FIRfilterResponseForBpf, input, bpf_w, INPUT_LENGTH, N); + + double* gainWithBpf = malloc(INPUT_LENGTH * sizeof(double)); + gain(gainWithBpf, FIRfilterResponseForBpf, gainForMid, INPUT_LENGTH); + + // Final audio + double* final_audio = malloc(INPUT_LENGTH * sizeof(double)); + add(final_audio, gainWithLpf, gainWithHpf, INPUT_LENGTH); + add(final_audio, final_audio, gainWithBpf, INPUT_LENGTH); + + // Print results + printf("Element at index 3: %f\n", final_audio[3]); + for (int i = 0; i < INPUT_LENGTH; i++) { + printf("%f ", final_audio[i]); + } + printf("\n"); + + // Free allocated memory + free(input); + free(lpf); + free(hamming_window); + free(lpf_w); + free(FIRfilterResponseForLpf); + free(gainWithLpf); + free(hpf); + free(hpf_w); + free(FIRfilterResponseForHpf); + free(gainWithHpf); + free(lpf2); + free(lpf2_w); + free(bpf_w); + free(FIRfilterResponseForBpf); + free(gainWithBpf); + free(final_audio); + + return 0; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/echocancelling.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/echocancelling.c new file mode 100644 index 000000000000..0ce15fd4a9cb --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/echocancelling.c @@ -0,0 +1,114 @@ +#include +#include +#include + +#define PI 3.14159265359 +#define INPUT_LENGTH 100000000 + +// Function to generate a range of values +void getRangeOfVector(double* vector, double start, int length, double increment) { + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; + } +} + +// Function to apply gain (multiplier) to a signal +void gain(double* output, double* input, double multiplier, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * multiplier; + } +} + +// Function to compute the sine of each element in the input array +void sine(double* output, double* input, int length) { + for (int i = 0; i < length; i++) { + output[i] = sin(input[i]); + } +} + +// Function to add two signals element-wise +void add(double* output, double* input1, double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] + input2[i]; + } +} + +// Function to delay the signal by a certain number of samples +void delay(double* input, double* output, int delaySamples, int length) { + for (int i = 0; i < length; i++) { + if (i < delaySamples) { + output[i] = 0; // Initial delay period is zeroed + } else { + output[i] = input[i - delaySamples]; + } + } +} + +// LMS filter response function +void lmsFilterResponse(double* output, double* noisy_sig, double* clean_sig, double mu, int filterSize, int length) { + double w[32] = {0}; // Initialize weights to zero + for (int n = 0; n < length; n++) { + double y = 0; + for (int i = 0; i < filterSize; i++) { + if (n - i >= 0) { + y += w[i] * noisy_sig[n - i]; + } + } + double e = clean_sig[n] - y; + for (int i = 0; i < filterSize; i++) { + if (n - i >= 0) { + w[i] += mu * e * noisy_sig[n - i]; + } + } + output[n] = y; + } +} + +int main() { + int fs = 8000; + double step = 1.0 / fs; + + // Allocate memory for vectors + double* input = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* getSinDuration = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* clean_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* noise = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* noisy_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* y = (double*)malloc(INPUT_LENGTH * sizeof(double)); + + // Generate input range + getRangeOfVector(input, 0.0, INPUT_LENGTH, step); + + // Generate clean signal + double f_sig = 500; + gain(getSinDuration, input, 2 * PI * f_sig, INPUT_LENGTH); + + sine(clean_sig, getSinDuration, INPUT_LENGTH); + + // Generate noise signal with a delay of 2 samples + delay(clean_sig, noise, 2, INPUT_LENGTH); + + // Create noisy signal by adding noise to clean signal + add(noisy_sig, clean_sig, noise, INPUT_LENGTH); + + // Apply LMS filter + double mu = 0.01; + int filterSize = 32; + + lmsFilterResponse(y, noisy_sig, clean_sig, mu, filterSize, INPUT_LENGTH); + + // Print result (for demonstration purposes) + for (int i = 0; i < INPUT_LENGTH && i < 10; i++) { // Limit print to first few samples + printf("%f\n", y[i]); + } + + // Free allocated memory + free(input); + free(getSinDuration); + free(clean_sig); + free(noise); + free(noisy_sig); + free(y); + + return 0; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/energyOfSignal.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/energyOfSignal.c new file mode 100644 index 000000000000..1b46d40711ec --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/energyOfSignal.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include + +#define INPUT_LENGTH 40000 +#define M_PI 3.14159265358979323846 + +double* getRange(double start, int noOfSamples, double increment) { + double* output = malloc(noOfSamples * sizeof(double)); + if (!output) { + perror("Memory allocation failed in getRange"); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < noOfSamples; i++) { + output[i] = start + i * increment; + } + + return output; +} + +void dft(double complex* output, const double* input, int length) { + for (int k = 0; k < length; k++) { + output[k] = 0; + for (int n = 0; n < length; n++) { + double angle = 2 * M_PI * k * n / length; + output[k] += input[n] * cexp(-I * angle); + } + } +} + +void square(double* output, const double* input, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * input[i]; + } +} + +double sum(const double* input, int length) { + double total = 0; + for (int i = 0; i < length; i++) { + total += input[i]; + } + return total; +} + +int main() { + double* input = getRange(0, INPUT_LENGTH, 1); + + double complex* fft = malloc(INPUT_LENGTH * sizeof(double complex)); + if (!fft) { + perror("Memory allocation failed"); + free(input); + return EXIT_FAILURE; + } + + dft(fft, input, INPUT_LENGTH); + + double* sq_abs = malloc(INPUT_LENGTH * sizeof(double)); + if (!sq_abs) { + perror("Memory allocation failed"); + free(input); + free(fft); + return EXIT_FAILURE; + } + + for (int i = 0; i < INPUT_LENGTH; i++) { + sq_abs[i] = creal(fft[i]) * creal(fft[i]) + cimag(fft[i]) * cimag(fft[i]); + } + + double sum_result = sum(sq_abs, INPUT_LENGTH); + double res = sum_result / INPUT_LENGTH; + + printf("%f\n", res); + + free(input); + free(fft); + free(sq_abs); + + return 0; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/hearingAid.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/hearingAid.c new file mode 100644 index 000000000000..71d5817b1c48 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/hearingAid.c @@ -0,0 +1,120 @@ +#include +#include +#include + +#define PI 3.14159265359 +#define INPUT_LENGTH 100000000 + +// Function to generate a range of values +void getRangeOfVector(double* vector, double start, int length, double increment) { + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; + } +} + +// Function to apply gain (multiplier) to a signal +void gain(double* output, double* input, double multiplier, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * multiplier; + } +} + +// Function to compute the sine of each element in the input array +void sine(double* output, double* input, int length) { + for (int i = 0; i < length; i++) { + output[i] = sin(input[i]); + } +} + +// Function to add two signals element-wise +void add(double* output, double* input1, double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] + input2[i]; + } +} + +// LMS filter response function +void lmsFilterResponse(double* output, double* noisy_sig, double* clean_sig, double mu, int filterSize, int length) { + double w[32] = {0}; // Initialize weights to zero + for (int n = 0; n < length; n++) { + double y = 0; + for (int i = 0; i < filterSize; i++) { + if (n - i >= 0) { + y += w[i] * noisy_sig[n - i]; + } + } + double e = clean_sig[n] - y; + for (int i = 0; i < filterSize; i++) { + if (n - i >= 0) { + w[i] += mu * e * noisy_sig[n - i]; + } + } + output[n] = e; + } +} + +int main() { + int fs = 8000; + double step = 1.0 / fs; + + // Allocate memory for vectors + double* input = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* getSinDuration = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* clean_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* getNoiseSinDuration = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* noise = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* noise1 = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* noisy_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* y = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* sol = (double*)malloc(INPUT_LENGTH * sizeof(double)); + + // Generate input range + getRangeOfVector(input, 0.0, INPUT_LENGTH, step); + + // Generate clean signal + double f_sig = 500; + gain(getSinDuration, input, 2 * PI * f_sig, INPUT_LENGTH); + + sine(clean_sig, getSinDuration, INPUT_LENGTH); + + // Generate noise signal with frequency of 3000 Hz + double f_noise = 3000; + gain(getNoiseSinDuration, input, 2 * PI * f_noise, INPUT_LENGTH); + + sine(noise, getNoiseSinDuration, INPUT_LENGTH); + + // Apply gain of 0.5 to the noise signal + gain(noise1, noise, 0.5, INPUT_LENGTH); + + // Create noisy signal by adding noise to clean signal + add(noisy_sig, clean_sig, noise1, INPUT_LENGTH); + + // Apply LMS filter + double mu = 0.01; + int filterSize = 32; + + lmsFilterResponse(y, noisy_sig, clean_sig, mu, filterSize, INPUT_LENGTH); + + // Apply final gain factor G1 to the LMS filter output + double G1 = 1002300; + + gain(sol, y, G1, INPUT_LENGTH); + + // Print result (for demonstration purposes) + for (int i = 0; i < INPUT_LENGTH; i++) { // Limit print to first few samples + printf("%f\n", sol[i]); + } + + // Free allocated memory + free(input); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + free(noise1); + free(noisy_sig); + free(y); + free(sol); + + return 0; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/lowPassFIRFilterDesign.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/lowPassFIRFilterDesign.c new file mode 100644 index 000000000000..79e8d376466b --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/lowPassFIRFilterDesign.c @@ -0,0 +1,75 @@ +#include +#include +#include + +#define INPUT_LENGTH 100000001 +#define PI M_PI +#define FC1 500 +#define FS 8000 + +double* hamming(int length) { + double* window = malloc(length * sizeof(double)); + if (!window) { + perror("Memory allocation failed in hamming"); + exit(EXIT_FAILURE); + } + for (int i = 0; i < length; i++) { + window[i] = 0.54 - 0.46 * cos(2 * PI * i / (length - 1)); + } + return window; +} + +double* lowPassFIRFilter(double wc, int length) { + double* filter = malloc(length * sizeof(double)); + if (!filter) { + perror("Memory allocation failed in lowPassFIRFilter"); + exit(EXIT_FAILURE); + } + + int mid = (length - 1) / 2; + for (int n = 0; n < length; n++) { + if (n == mid) { + filter[n] = wc / PI; + } else { + filter[n] = sin(wc * (n - mid)) / (PI * (n - mid)); + } + } + return filter; +} + +void elementWiseMultiplication(double* output, const double* array1, const double* array2, int length) { + for (int i = 0; i < length; i++) { + output[i] = array1[i] * array2[i]; + } +} + +double getElemAtIndx(const double* array, int index) { + return array[index]; +} + +int main() { + double wc1 = 2 * PI * FC1 / FS; + + double* lpf = lowPassFIRFilter(wc1, INPUT_LENGTH); + double* hamming_window = hamming(INPUT_LENGTH); + + double* lpf_w = malloc(INPUT_LENGTH * sizeof(double)); + if (!lpf_w) { + perror("Memory allocation failed for lpf_w"); + free(lpf); + free(hamming_window); + exit(EXIT_FAILURE); + } + + elementWiseMultiplication(lpf_w, lpf, hamming_window, INPUT_LENGTH); + + double final1 = getElemAtIndx(lpf_w, 6); + + printf("%f\n", final1); + + free(lpf); + free(hamming_window); + free(lpf_w); + + return 0; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/lowPassFull1.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/lowPassFull1.c new file mode 100644 index 000000000000..7a955939a33e --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/lowPassFull1.c @@ -0,0 +1,147 @@ +#include +#include +#include + +#define PI 3.14159265359 +#define INPUT_LENGTH 10000 + +double* getRangeOfVector(double start, int noOfSamples, double increment) { + double* output = malloc(noOfSamples * sizeof(double)); + if (!output) { + perror("Memory allocation failed in getRangeOfVector"); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < noOfSamples; i++) { + output[i] = start + i * increment; + } + + return output; +} + +void gain(double* output, double* input, double multiplier, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * multiplier; + } +} + +void elementWiseAdd(double* output, double* input1, double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] + input2[i]; + } +} + +void elementWiseMultiply(double* output, double* input1, double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] * input2[i]; + } +} + +void lowPassFIRFilter(double* lpf, double wc, int N) { + int mid = (N - 1) / 2; + for (int n = 0; n < N; n++) { + if (n == mid) { + lpf[n] = wc / PI; + } else { + lpf[n] = (wc / PI) * sin(wc * (n - mid)) / (wc * (n - mid)); + } + } +} + +void hammingWindow(double* hamming, int N) { + for (int n = 0; n < N; n++) { + hamming[n] = 0.54 - 0.46 * cos(2 * PI * n / (N - 1)); + } +} + +void FIRFilterResponse(double* output, double* input, double* filter, int input_length, int filter_length) { + int i, j; + for (i = 0; i < input_length; i++) { + output[i] = 0; + for (j = 0; j < filter_length; j++) { + if (i - j >= 0) { + output[i] += input[i - j] * filter[j]; + } + } + } +} + +int main() { + int fs = 8000; + + // Allocate memory dynamically + double* input = getRangeOfVector(0, INPUT_LENGTH, 0.000125); + + // Allocate other large arrays dynamically + double* getSinDuration = malloc(INPUT_LENGTH * sizeof(double)); + double* clean_sig = malloc(INPUT_LENGTH * sizeof(double)); + double* getNoiseSinDuration = malloc(INPUT_LENGTH * sizeof(double)); + double* noise = malloc(INPUT_LENGTH * sizeof(double)); + double* noisy_sig = malloc(INPUT_LENGTH * sizeof(double)); + double* scaled_noise = malloc(INPUT_LENGTH * sizeof(double)); + double* FIRfilterResponse = malloc(INPUT_LENGTH * sizeof(double)); + + // Check if memory allocation was successful + if (!getSinDuration || !clean_sig || !getNoiseSinDuration || !noise || !noisy_sig || !scaled_noise || !FIRfilterResponse) { + perror("Memory allocation failed"); + free(input); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + free(noisy_sig); + free(scaled_noise); + free(FIRfilterResponse); + exit(EXIT_FAILURE); + } + + // Signal processing steps + double f_sig = 500; + gain(getSinDuration, input, 2 * PI * f_sig, INPUT_LENGTH); + + for (int i = 0; i < INPUT_LENGTH; i++) { + clean_sig[i] = sin(getSinDuration[i]); + } + + double f_noise = 3000; + gain(getNoiseSinDuration, input, 2 * PI * f_noise, INPUT_LENGTH); + + for (int i = 0; i < INPUT_LENGTH; i++) { + noise[i] = sin(getNoiseSinDuration[i]); + } + + gain(scaled_noise, noise, 0.5, INPUT_LENGTH); + elementWiseAdd(noisy_sig, clean_sig, scaled_noise, INPUT_LENGTH); + + // Filter design + double fc = 1000; + double wc = 2 * PI * fc / fs; + int N = 101; + + double lpf[N]; + lowPassFIRFilter(lpf, wc, N); + + double hamming[N]; + hammingWindow(hamming, N); + + double lpf_w[N]; + elementWiseMultiply(lpf_w, lpf, hamming, N); + + FIRFilterResponse(FIRfilterResponse, noisy_sig, lpf_w, INPUT_LENGTH, N); + + for (int i = 0; i < INPUT_LENGTH; i++) { + printf("%f\n", FIRfilterResponse[i]); + } + + // Free allocated memory at the end + free(input); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + free(noisy_sig); + free(scaled_noise); + free(FIRfilterResponse); + + return 0; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/noisecancelling.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/noisecancelling.c new file mode 100644 index 000000000000..184652a9bf5d --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/noisecancelling.c @@ -0,0 +1,117 @@ +#include +#include +#include + +#define INPUT_LENGTH 100000000 + +void getRangeOfVector(double* vector, double start, int length, double increment) { + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; + } +} + +void gain(double* output, double* input, double multiplier, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * multiplier; + } +} + +void sine(double* output, double* input, int length) { + for (int i = 0; i < length; i++) { + output[i] = sin(input[i]); + } +} + +void add(double* output, double* input1, double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] + input2[i]; + } +} + +void lmsFilterResponse(double* output, double* noisy_sig, double* clean_sig, double mu, int filterSize, int length) { + double w[32] = {0}; + for (int n = 0; n < length; n++) { + double y = 0; + for (int i = 0; i < filterSize; i++) { + if (n - i >= 0) { + y += w[i] * noisy_sig[n - i]; + } + } + double e = clean_sig[n] - y; + for (int i = 0; i < filterSize; i++) { + if (n - i >= 0) { + w[i] += mu * e * noisy_sig[n - i]; + } + } + output[n] = y; + } +} + +int main() { + // Allocate memory dynamically + double* t = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* getSinDuration = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* clean_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* getNoiseSinDuration = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* noise = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* noise1 = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* noisy_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* y = (double*)malloc(INPUT_LENGTH * sizeof(double)); + double* sol = (double*)malloc(INPUT_LENGTH * sizeof(double)); + + // Check if memory allocation was successful + if (!t || !getSinDuration || !clean_sig || !getNoiseSinDuration || !noise || !noise1 || !noisy_sig || !y || !sol) { + perror("Memory allocation failed"); + free(t); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + free(noise1); + free(noisy_sig); + free(y); + free(sol); + exit(EXIT_FAILURE); + } + + // Signal processing steps + getRangeOfVector(t, 0, INPUT_LENGTH, 0.000125); + + double f_sig = 500; + double pi = 3.14159265359; + gain(getSinDuration, t, 2 * pi * f_sig, INPUT_LENGTH); + + sine(clean_sig, getSinDuration, INPUT_LENGTH); + + double f_noise = 3000; + gain(getNoiseSinDuration, t, 2 * pi * f_noise, INPUT_LENGTH); + + sine(noise, getNoiseSinDuration, INPUT_LENGTH); + + gain(noise1, noise, 0.5, INPUT_LENGTH); + + add(noisy_sig, clean_sig, noise1, INPUT_LENGTH); + + // LMS filter response + lmsFilterResponse(y, noisy_sig, clean_sig, 0.01, 32, INPUT_LENGTH); + + gain(sol, y, 10, INPUT_LENGTH); + + // Print the filtered signal + for (int i = 0; i < INPUT_LENGTH; i++) { + printf("%f\n", sol[i]); + } + + // Free allocated memory at the end + free(t); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + free(noise1); + free(noisy_sig); + free(y); + free(sol); + + return 0; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/periodogram2Conv.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/periodogram2Conv.c similarity index 62% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/periodogram2Conv.c rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/periodogram2Conv.c index f1b201ccf8d2..5af6d73a1b2c 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/periodogram2Conv.c +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/periodogram2Conv.c @@ -1,9 +1,12 @@ #include #include -void getRangeOfVector(double* input, int start, int NoOfElements, double Increment) { - for (int i = 0; i < NoOfElements; i++) { - input[i] = start + i * Increment; +// Define INPUT_LENGTH globally +#define INPUT_LENGTH 50000 + +void getRangeOfVector(double* vector, double start, int length, double increment) { + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; } } @@ -51,27 +54,27 @@ void squareMagnitude(double* output, double* real, double* imag, int length) { } int main() { - int length = 10; - double input[10]; - getRangeOfVector(input, 0, length, 1); + // Use INPUT_LENGTH instead of hard-coded value + double input[INPUT_LENGTH]; + getRangeOfVector(input, 0.0, INPUT_LENGTH, 1.0); - double reverse_input[10]; - reverseInput(reverse_input, input, length); + double reverse_input[INPUT_LENGTH]; + reverseInput(reverse_input, input, INPUT_LENGTH); - double conv1d[10]; - FIRFilterResponse(conv1d, input, reverse_input, length); + double conv1d[INPUT_LENGTH]; + FIRFilterResponse(conv1d, input, reverse_input, INPUT_LENGTH); - double fft_real[10]; - double fft_img[10]; - dftReal(fft_real, conv1d, length); - dftImag(fft_img, conv1d, length); + double fft_real[INPUT_LENGTH]; + double fft_img[INPUT_LENGTH]; + dftReal(fft_real, conv1d, INPUT_LENGTH); + dftImag(fft_img, conv1d, INPUT_LENGTH); - double sq[10]; - squareMagnitude(sq, fft_real, fft_img, length); + double sq[INPUT_LENGTH]; + squareMagnitude(sq, fft_real, fft_img, INPUT_LENGTH); - for (int i = 0; i < length; i++) { + for (int i = 0; i < INPUT_LENGTH; i++) { printf("%f\n", sq[i]); } return 0; -} +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/underWaterCommunication.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/underWaterCommunication.c new file mode 100644 index 000000000000..4568533e5280 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/underWaterCommunication.c @@ -0,0 +1,165 @@ +#include +#include +#include + +#define PI 3.14159265359 +#define INPUT_LENGTH 100000000 +#define FILTER_ORDER 5 + +// Function prototypes +double* getRangeOfVector(double start, int length, double increment); +void gain(double* output, const double* input, double multiplier, int length); +void sine(double* output, const double* input, int length); +void delay(double* output, const double* input, int delaySamples, int length); +void add(double* output, const double* input1, const double* input2, int length); +double* lowPassFIRFilter(double wc, int length); +double* hamming(int length); +void FIRFilterResponse(double* output, const double* input, const double* filter, int inputLength, int filterLength); +void thresholdUp(double* output, const double* input, double threshold, double defaultValue, int length); + +// Function implementations +double* getRangeOfVector(double start, int length, double increment) { + double* vector = malloc(length * sizeof(double)); + if (!vector) { + perror("Memory allocation failed in getRangeOfVector"); + exit(EXIT_FAILURE); + } + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; + } + return vector; +} + +void gain(double* output, const double* input, double multiplier, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * multiplier; + } +} + +void sine(double* output, const double* input, int length) { + for (int i = 0; i < length; i++) { + output[i] = sin(input[i]); + } +} + +void delay(double* output, const double* input, int delaySamples, int length) { + for (int i = 0; i < length; i++) { + if (i < delaySamples) { + output[i] = 0; + } else { + output[i] = input[i - delaySamples]; + } + } +} + +void add(double* output, const double* input1, const double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] + input2[i]; + } +} + +double sinc(double x) { + if (x == 0) return 1.0; + return sin(x) / x; +} + +double* lowPassFIRFilter(double wc, int length) { + double* filter = malloc(length * sizeof(double)); + if (!filter) { + perror("Memory allocation failed in lowPassFIRFilter"); + exit(EXIT_FAILURE); + } + int mid = (length - 1) / 2; + for (int n = 0; n < length; n++) { + if (n == mid) { + filter[n] = wc / PI; + } else { + filter[n] = sinc(wc * (n - mid)) * wc / PI; + } + } + return filter; +} + +double* hamming(int length) { + double* window = malloc(length * sizeof(double)); + if (!window) { + perror("Memory allocation failed in hamming"); + exit(EXIT_FAILURE); + } + for (int i = 0; i < length; i++) { + window[i] = 0.54 - 0.46 * cos(2 * PI * i / (length - 1)); + } + return window; +} + +void FIRFilterResponse(double* output, const double* input, const double* filter, int inputLength, int filterLength) { + for (int i = 0; i < inputLength; i++) { + output[i] = 0; + for (int j = 0; j < filterLength; j++) { + if (i - j >= 0) { + output[i] += input[i - j] * filter[j]; + } + } + } +} + +void thresholdUp(double* output, const double* input, double threshold, double defaultValue, int length) { + for (int i = 0; i < length; i++) { + output[i] = (input[i] >= threshold) ? input[i] : defaultValue; + } +} + +int main() { + int fs = 1000; + double* input = getRangeOfVector(0, INPUT_LENGTH, 1); + + double getMultiplier = 2 * PI * 5; + double* getSinDuration = malloc(INPUT_LENGTH * sizeof(double)); + gain(getSinDuration, input, getMultiplier, INPUT_LENGTH); + + double* signal = malloc(INPUT_LENGTH * sizeof(double)); + sine(signal, getSinDuration, INPUT_LENGTH); + + double* noise = malloc(INPUT_LENGTH * sizeof(double)); + delay(noise, signal, 5, INPUT_LENGTH); + + double* noisy_sig = malloc(INPUT_LENGTH * sizeof(double)); + add(noisy_sig, signal, noise, INPUT_LENGTH); + + double fc = 1000; + double wc = 2 * PI * fc / 500; // wc should vary from 0 to pi + + double* lpf = lowPassFIRFilter(wc, FILTER_ORDER); + double* hamming_window = hamming(FILTER_ORDER); + + double* lpf_w = malloc(FILTER_ORDER * sizeof(double)); + for (int i = 0; i < FILTER_ORDER; i++) { + lpf_w[i] = lpf[i] * hamming_window[i]; + } + + double* FIRfilterResponse = malloc(INPUT_LENGTH * sizeof(double)); + FIRFilterResponse(FIRfilterResponse, noisy_sig, lpf_w, INPUT_LENGTH, FILTER_ORDER); + + double threshold = 0.5; + double* GetThresholdReal = malloc(INPUT_LENGTH * sizeof(double)); + thresholdUp(GetThresholdReal, FIRfilterResponse, threshold, 0, INPUT_LENGTH); + + for (int i = 0; i < INPUT_LENGTH; i++) { + printf("%f ", GetThresholdReal[i]); + } + printf("\n"); + + // Free allocated memory + free(input); + free(getSinDuration); + free(signal); + free(noise); + free(noisy_sig); + free(lpf); + free(hamming_window); + free(lpf_w); + free(FIRfilterResponse); + free(GetThresholdReal); + + return 0; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/vibrationAnalysis.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/vibrationAnalysis.c new file mode 100644 index 000000000000..ffdee5ec4c51 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/vibrationAnalysis.c @@ -0,0 +1,174 @@ +#include +#include +#include +#include + +#define PI 3.14159265359 +#define INPUT_LENGTH 100000 + +// Function prototypes +double* getRangeOfVector(double start, int length, double increment); +void gain(double* output, const double* input, double multiplier, int length); +void sine(double* output, const double* input, int length); +void add(double* output, const double* input1, const double* input2, int length); +void delay(double* output, const double* input, int delaySamples, int length); +void dft(double complex* output, const double* input, int length); +void square(double* output, const double* input, int length); +double sum(const double* input, int length); +void threshold(double* output, const double* input, double thresholdValue, int length); + +// Function implementations +double* getRangeOfVector(double start, int length, double increment) { + double* vector = malloc(length * sizeof(double)); + if (!vector) { + perror("Memory allocation failed in getRangeOfVector"); + exit(EXIT_FAILURE); + } + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; + } + return vector; +} + +void gain(double* output, const double* input, double multiplier, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * multiplier; + } +} + +void sine(double* output, const double* input, int length) { + for (int i = 0; i < length; i++) { + output[i] = sin(input[i]); + } +} + +void add(double* output, const double* input1, const double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] + input2[i]; + } +} + +void delay(double* output, const double* input, int delaySamples, int length) { + for (int i = 0; i < length; i++) { + if (i < delaySamples) { + output[i] = 0; + } else { + output[i] = input[i - delaySamples]; + } + } +} + +void dft(double complex* output, const double* input, int length) { + for (int k = 0; k < length; k++) { + output[k] = 0; + for (int n = 0; n < length; n++) { + double angle = 2 * PI * k * n / length; + output[k] += input[n] * cexp(-I * angle); + } + } +} + +void square(double* output, const double* input, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * input[i]; + } +} + +double sum(const double* input, int length) { + double total = 0; + for (int i = 0; i < length; i++) { + total += input[i]; + } + return total; +} + +void threshold(double* output, const double* input, double thresholdValue, int length) { + for (int i = 0; i < length; i++) { + if (input[i] <= -thresholdValue || input[i] >= thresholdValue) { + output[i] = input[i]; + } else { + output[i] = 0; + } + } +} + +int main() { + int fs = 1000; + double* input = getRangeOfVector(0, INPUT_LENGTH, 1); + + double getMultiplier = 2 * PI * 50; + double* getSinDuration = malloc(INPUT_LENGTH * sizeof(double)); + gain(getSinDuration, input, getMultiplier, INPUT_LENGTH); + + double* sig1 = malloc(INPUT_LENGTH * sizeof(double)); + sine(sig1, getSinDuration, INPUT_LENGTH); + + double getMultiplier2 = 2 * PI * 120; + double* getSinDuration2 = malloc(INPUT_LENGTH * sizeof(double)); + gain(getSinDuration2, input, getMultiplier2, INPUT_LENGTH); + + double* sinsig2 = malloc(INPUT_LENGTH * sizeof(double)); + sine(sinsig2, getSinDuration2, INPUT_LENGTH); + + double* sig2 = malloc(INPUT_LENGTH * sizeof(double)); + gain(sig2, sinsig2, 0.5, INPUT_LENGTH); + + double* signal = malloc(INPUT_LENGTH * sizeof(double)); + add(signal, sig1, sig2, INPUT_LENGTH); + + double* noise = malloc(INPUT_LENGTH * sizeof(double)); + delay(noise, signal, 5, INPUT_LENGTH); + + double* noisy_sig = malloc(INPUT_LENGTH * sizeof(double)); + add(noisy_sig, signal, noise, INPUT_LENGTH); + + double threshold_value = 0.2; + + double complex* dft_output = malloc(INPUT_LENGTH * sizeof(double complex)); + dft(dft_output, noisy_sig, INPUT_LENGTH); + + double* fft_real = malloc(INPUT_LENGTH * sizeof(double)); + double* fft_img = malloc(INPUT_LENGTH * sizeof(double)); + for (int i = 0; i < INPUT_LENGTH; i++) { + fft_real[i] = creal(dft_output[i]); + fft_img[i] = cimag(dft_output[i]); + } + + double* sq_abs = malloc(INPUT_LENGTH * sizeof(double)); + double* temp_real = malloc(INPUT_LENGTH * sizeof(double)); + double* temp_img = malloc(INPUT_LENGTH * sizeof(double)); + square(temp_real, fft_real, INPUT_LENGTH); + square(temp_img, fft_img, INPUT_LENGTH); + add(sq_abs, temp_real, temp_img, INPUT_LENGTH); + + double sum1 = sum(sq_abs, INPUT_LENGTH); + double res = sum1 / INPUT_LENGTH; + + double* GetThresholdReal = malloc(INPUT_LENGTH * sizeof(double)); + threshold(GetThresholdReal, sq_abs, threshold_value, INPUT_LENGTH); + + for (int i = 0; i < INPUT_LENGTH; i++) { + printf("%f ", GetThresholdReal[i]); + } + printf("\n"); + + // Free allocated memory + free(input); + free(getSinDuration); + free(sig1); + free(getSinDuration2); + free(sinsig2); + free(sig2); + free(signal); + free(noise); + free(noisy_sig); + free(dft_output); + free(fft_real); + free(fft_img); + free(sq_abs); + free(temp_real); + free(temp_img); + free(GetThresholdReal); + + return 0; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/voiceActivityDetection.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/voiceActivityDetection.c new file mode 100644 index 000000000000..ca54d6e91b10 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/voiceActivityDetection.c @@ -0,0 +1,117 @@ +#include +#include +#include + +#define PI 3.14159265359 +#define INPUT_LENGTH 100000000 + +double* getRangeOfVector(double start, int length, double increment); +void gain(double* output, const double* input, double multiplier, int length); +void sine(double* output, const double* input, int length); +void delay(double* output, const double* input, int delaySamples, int length); +void add(double* output, const double* input1, const double* input2, int length); +void threshold(double* output, const double* input, double thresholdValue, int length); +int zeroCrossCount(const double* input, int length); + +// Function implementations +double* getRangeOfVector(double start, int length, double increment) { + double* vector = malloc(length * sizeof(double)); + if (!vector) { + perror("Memory allocation failed in getRangeOfVector"); + exit(EXIT_FAILURE); + } + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; + } + return vector; +} + +void gain(double* output, const double* input, double multiplier, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * multiplier; + } +} + +void sine(double* output, const double* input, int length) { + for (int i = 0; i < length; i++) { + output[i] = sin(input[i]); + } +} + +void delay(double* output, const double* input, int delaySamples, int length) { + for (int i = 0; i < length; i++) { + if (i < delaySamples) { + output[i] = 0; + } else { + output[i] = input[i - delaySamples]; + } + } +} + +void add(double* output, const double* input1, const double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] + input2[i]; + } +} + +void threshold(double* output, const double* input, double thresholdValue, int length) { + for (int i = 0; i < length; i++) { + if (input[i] <= -thresholdValue || input[i] >= thresholdValue) { + output[i] = input[i]; + } else { + output[i] = 0; + } + } +} + +int zeroCrossCount(const double* input, int length) { + int count = 0; + for (int i = 1; i < length; i++) { + if ((input[i-1] > 0 && input[i] <= 0) || (input[i-1] < 0 && input[i] >= 0)) { + count++; + } + } + return count; +} + +int main() { + int fs = 1000; + double* input = getRangeOfVector(0, INPUT_LENGTH, 1); + + double getMultiplier = 2 * PI * 5; + double* getSinDuration = malloc(INPUT_LENGTH * sizeof(double)); + gain(getSinDuration, input, getMultiplier, INPUT_LENGTH); + + double* signal = malloc(INPUT_LENGTH * sizeof(double)); + sine(signal, getSinDuration, INPUT_LENGTH); + + double* noise = malloc(INPUT_LENGTH * sizeof(double)); + delay(noise, signal, 5, INPUT_LENGTH); + + double* noisy_sig = malloc(INPUT_LENGTH * sizeof(double)); + add(noisy_sig, signal, noise, INPUT_LENGTH); + + double threshold_value = 0.8; + double* GetThresholdReal = malloc(INPUT_LENGTH * sizeof(double)); + threshold(GetThresholdReal, noisy_sig, threshold_value, INPUT_LENGTH); + + int zcr = zeroCrossCount(GetThresholdReal, INPUT_LENGTH); + + for (int i = 0; i < INPUT_LENGTH; i++) { + printf("%f ", GetThresholdReal[i]); + } + printf("\n"); + + // Print zero-crossing count + printf("Zero-crossing count: %d\n", zcr); + + // Free allocated memory + free(input); + free(getSinDuration); + free(signal); + free(noise); + free(noisy_sig); + free(GetThresholdReal); + + return 0; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CountLinesFile.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CountLinesFile.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CountLinesFile.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CountLinesFile.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/ExtractOpName.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/ExtractOpName.py similarity index 90% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/ExtractOpName.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/ExtractOpName.py index a7962eb4c8c0..6fe49536d05d 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/ExtractOpName.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/ExtractOpName.py @@ -2,9 +2,9 @@ import os fileNamePath = "mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td" -BasePathForLLVM = "/mnt/sharedDrive/SourceCode/llvm-project/" +# BasePathForLLVM = "/mnt/sharedDrive/SourceCode/llvm-project/" # OutputScriptPath = "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/" - +BasePathForLLVM = "/home/local/ASURITE/apkhedka/ForLLVM/" fileName = BasePathForLLVM + fileNamePath print(fileName) # Create 'Output' folder if it doesn't exist diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/HammingWindow.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/HammingWindow.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/HammingWindow.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/HammingWindow.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/LMSNoiseFilter.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/LMSNoiseFilter.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/LMSNoiseFilter.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/LMSNoiseFilter.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PyDSL/audioCompression.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/audioCompression.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PyDSL/audioCompression.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/audioCompression.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PyDSL/energyOfSignal.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/energyOfSignal.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PyDSL/energyOfSignal.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/energyOfSignal.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PyDSL/lowPassFIRFilterDesign1.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/lowPassFIRFilterDesign1.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PyDSL/lowPassFIRFilterDesign1.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/lowPassFIRFilterDesign1.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PyDSL/lowPassFull1.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/lowPassFull1.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PyDSL/lowPassFull1.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/lowPassFull1.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PyDSL/noisecancelling.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/noisecancelling.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PyDSL/noisecancelling.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/noisecancelling.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PyDSL/periodogram2Conv.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/periodogram2Conv.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PyDSL/periodogram2Conv.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/periodogram2Conv.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Quantization.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Quantization.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Quantization.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Quantization.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/TryHearingAid copy.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/TryHearingAid copy.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/TryHearingAid copy.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/TryHearingAid copy.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/TryHearingAid.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/TryHearingAid.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/TryHearingAid.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/TryHearingAid.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/audioEqualizer.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/audioEqualizer.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/audioEqualizer.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/audioEqualizer.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/bandPassfilter.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/bandPassfilter.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/bandPassfilter.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/bandPassfilter.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/filterDesign.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/filterDesign.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/filterDesign.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/filterDesign.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/hearingAid.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/hearingAid.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/hearingAid.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/hearingAid.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/highPassfilter.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/highPassfilter.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/highPassfilter.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/highPassfilter.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/lmsNoiseCancelling.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/lmsNoiseCancelling.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/lmsNoiseCancelling.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/lmsNoiseCancelling.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/lowPassFilterApp.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/lowPassFilterApp.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/lowPassFilterApp.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/lowPassFilterApp.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/periodogramHelp.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/periodogramHelp.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/periodogramHelp.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/periodogramHelp.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/periodogramHelp2.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/periodogramHelp2.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/periodogramHelp2.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/periodogramHelp2.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/audioCompression.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/audioCompression.c deleted file mode 100644 index 24bcd1f4030b..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/audioCompression.c +++ /dev/null @@ -1,107 +0,0 @@ -#include -#include -#include - -void getRangeOfVector(double* input, int start, int NoOfElements, double Increment) { - for (int i = 0; i < NoOfElements; i++) { - input[i] = start + i * Increment; - } -} - -void dftReal(double* real, double* input, int length) { - for (int k = 0; k < length; k++) { - real[k] = 0; - for (int n = 0; n < length; n++) { - double angle = 2 * M_PI * k * n / length; - real[k] += input[n] * cos(angle); - } - } -} - -void dftImag(double* imag, double* input, int length) { - for (int k = 0; k < length; k++) { - imag[k] = 0; - for (int n = 0; n < length; n++) { - double angle = 2 * M_PI * k * n / length; - imag[k] -= input[n] * sin(angle); - } - } -} - -void threshold(double* output, double* input, double thresh, int length) { - for (int i = 0; i < length; i++) { - if (input[i] >= thresh || input[i] <= -thresh) { - output[i] = input[i]; - } else { - output[i] = 0; - } - } -} - -void quantization(double* output, double* input, int nlevels, double max, double min, int length) { - double step = (max - min) / nlevels; - for (int i = 0; i < length; i++) { - output[i] = round((input[i] - min) / step) * step + min; - } -} - -int* runLenEncoding(double* input, int length, int* rleLength) { - int* rle = (int*)malloc(length * sizeof(int)); - int index = 0; - for (int i = 1; i < length; i++) { - if (input[i] != input[i - 1]) { - rle[index++] = input[i - 1]; - rle[index++] = 1; - } else { - rle[index - 1]++; - } - } - *rleLength = index; - return rle; -} - -double getElemAtIndx(int* rle, int indx) { - return rle[indx]; -} - -int main() { - int input_length = 50000; - double input[50000]; - getRangeOfVector(input, 0, input_length, 1); - - int nlevels = 16; - double min = 0; - double max = 8; - - double threshold_val = 4; - - double fft10real[50000]; - double fft10img[50000]; - - dftReal(fft10real, fft10img, input, input_length); - dftImag(fft10real, fft10img, input, input_length); - - double GetThresholdReal[50000]; - double GetThresholdImg[50000]; - threshold(GetThresholdReal, fft10real, threshold_val, input_length); - threshold(GetThresholdImg, fft10img, threshold_val, input_length); - - double QuantOutReal[50000]; - double QuantOutImg[50000]; - quantization(QuantOutReal, GetThresholdReal, nlevels, max, min, input_length); - quantization(QuantOutImg, GetThresholdImg, nlevels, max, min, input_length); - - int rleLengthReal, rleLengthImg; - int* rLEOutReal = runLenEncoding(QuantOutReal, input_length, &rleLengthReal); - int* rLEOutImg = runLenEncoding(QuantOutImg, input_length, &rleLengthImg); - - double final1 = getElemAtIndx(rLEOutReal, 6); - double final2 = getElemAtIndx(rLEOutImg, 7); - printf("%f\n", final1); - printf("%f\n", final2); - - free(rLEOutReal); - free(rLEOutImg); - - return 0; -} diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/energyOfSignal.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/energyOfSignal.c deleted file mode 100644 index f95be8171a11..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/energyOfSignal.c +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include - -void getRangeOfVector(double* input, int start, int NoOfElements, double Increment) { - for (int i = 0; i < NoOfElements; i++) { - input[i] = start + i * Increment; - } -} - -void dftReal(double* real, double* input, int length) { - for (int k = 0; k < length; k++) { - real[k] = 0; - for (int n = 0; n < length; n++) { - double angle = 2 * M_PI * k * n / length; - real[k] += input[n] * cos(angle); - } - } -} - -void dftImag(double* imag, double* input, int length) { - for (int k = 0; k < length; k++) { - imag[k] = 0; - for (int n = 0; n < length; n++) { - double angle = 2 * M_PI * k * n / length; - imag[k] -= input[n] * sin(angle); - } - } -} - -void square(double* output, double* input, int length) { - for (int i = 0; i < length; i++) { - output[i] = input[i] * input[i]; - } -} - -double sum(double* input, int length) { - double total = 0; - for (int i = 0; i < length; i++) { - total += input[i]; - } - return total; -} - -int len(double* input) { - return sizeof(input) / sizeof(input[0]); -} - -int main() { - int input_length = 10; - double input[10]; - getRangeOfVector(input, 0, input_length, 1); - - double fft_real[10]; - double fft_img[10]; - dftReal(fft_real, input, input_length); - dftImag(fft_img, input, input_length); - - double sq_real[10]; - double sq_img[10]; - square(sq_real, fft_real, input_length); - square(sq_img, fft_img, input_length); - - double sq_abs[10]; - for (int i = 0; i < input_length; i++) { - sq_abs[i] = sq_real[i] + sq_img[i]; - } - - double sum1 = sum(sq_abs, input_length); - int len1 = input_length; - double res = sum1 / len1; - - printf("%f\n", res); - - return 0; -} diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/lowPassFIRFilterDesign.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/lowPassFIRFilterDesign.c deleted file mode 100644 index 6b8d6b49b9d4..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/lowPassFIRFilterDesign.c +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include -#include - -void hamming(double* window, int N) { - for (int i = 0; i < N; i++) { - window[i] = 0.54 - 0.46 * cos(2 * M_PI * i / (N - 1)); - } -} - -void lowPassFIRFilter(double* filter, double wc, int N) { - int mid = (N - 1) / 2; - for (int n = 0; n < N; n++) { - if (n == mid) { - filter[n] = wc / M_PI; - } else { - filter[n] = sin(wc * (n - mid)) / (M_PI * (n - mid)); - } - } -} - -void elementWiseMultiplication(double* output, double* array1, double* array2, int N) { - for (int i = 0; i < N; i++) { - output[i] = array1[i] * array2[i]; - } -} - -double getElemAtIndx(double* array, int index) { - return array[index]; -} - -int main() { - int N = 51; - double pi = 3.14159265359; - double fc1 = 500; - double Fs = 8000; - double wc1 = 2 * pi * fc1 / Fs; - - double lpf[51]; - lowPassFIRFilter(lpf, wc1, N); - - double hamming_window[51]; - hamming(hamming_window, N); - - double lpf_w[51]; - elementWiseMultiplication(lpf_w, lpf, hamming_window, N); - - double final1 = getElemAtIndx(lpf_w, 6); - - printf("%f\n", final1); - - return 0; -} diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/lowPassFull1.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/lowPassFull1.c deleted file mode 100644 index f00a7f8c1bcf..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/lowPassFull1.c +++ /dev/null @@ -1,110 +0,0 @@ -#include -#include - -#define PI 3.14159265359 - -void getRangeOfVector(double* input, int start, int NoOfElements, double Increment) { - for (int i = 0; i < NoOfElements; i++) { - input[i] = start + i * Increment; - } -} - -void gain(double* output, double* input, double multiplier, int length) { - for (int i = 0; i < length; i++) { - output[i] = input[i] * multiplier; - } -} - -void elementWiseAdd(double* output, double* input1, double* input2, int length) { - for (int i = 0; i < length; i++) { - output[i] = input1[i] + input2[i]; - } -} - -void elementWiseMultiply(double* output, double* input1, double* input2, int length) { - for (int i = 0; i < length; i++) { - output[i] = input1[i] * input2[i]; - } -} - -void lowPassFIRFilter(double* lpf, double wc, int N) { - int mid = (N - 1) / 2; - for (int n = 0; n < N; n++) { - if (n == mid) { - lpf[n] = wc / PI; - } else { - lpf[n] = (wc / PI) * sin(wc * (n - mid)) / (wc * (n - mid)); - } - } -} - -void hammingWindow(double* hamming, int N) { - for (int n = 0; n < N; n++) { - hamming[n] = 0.54 - 0.46 * cos(2 * PI * n / (N - 1)); - } -} - -void FIRFilterResponse(double* output, double* input, double* filter, int input_length, int filter_length) { - int i, j; - for (i = 0; i < input_length; i++) { - output[i] = 0; - for (j = 0; j < filter_length; j++) { - if (i - j >= 0) { - output[i] += input[i - j] * filter[j]; - } - } - } -} - -int main() { - int fs = 8000; - int input_length = 30; - double input[30]; - getRangeOfVector(input, 0, input_length, 0.000125); - - double f_sig = 500; - double getMultiplier = 2 * PI * f_sig; - - double getSinDuration[30]; - gain(getSinDuration, input, getMultiplier, input_length); - - double clean_sig[30]; - for (int i = 0; i < input_length; i++) { - clean_sig[i] = sin(getSinDuration[i]); - } - - double f_noise = 3000; - double getNoiseSinDuration[30]; - gain(getNoiseSinDuration, input, 2 * PI * f_noise, input_length); - - double noise[30]; - for (int i = 0; i < input_length; i++) { - noise[i] = sin(getNoiseSinDuration[i]); - } - - double noisy_sig[30]; - double scaled_noise[30]; - gain(scaled_noise, noise, 0.5, input_length); - elementWiseAdd(noisy_sig, clean_sig, scaled_noise, input_length); - - double fc = 1000; - double wc = 2 * PI * fc / fs; - int N = 101; - double lpf[101]; - lowPassFIRFilter(lpf, wc, N); - - double hamming[101]; - hammingWindow(hamming, N); - - double lpf_w[101]; - elementWiseMultiply(lpf_w, lpf, hamming, N); - - double FIRfilterResponse[30]; - FIRFilterResponse(FIRfilterResponse, noisy_sig, lpf_w, input_length, N); - - for (int i = 0; i < input_length; i++) { - printf("%f\n", FIRfilterResponse[i]); - } - - return 0; -} diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/noisecancelling.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/noisecancelling.c deleted file mode 100644 index 235d64705c05..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/noisecancelling.c +++ /dev/null @@ -1,91 +0,0 @@ -#include -#include - -void getRangeOfVector(double* vector, double start, int length, double increment) { - for (int i = 0; i < length; i++) { - vector[i] = start + i * increment; - } -} - -void gain(double* output, double* input, double multiplier, int length) { - for (int i = 0; i < length; i++) { - output[i] = input[i] * multiplier; - } -} - -void sine(double* output, double* input, int length) { - for (int i = 0; i < length; i++) { - output[i] = sin(input[i]); - } -} - -void add(double* output, double* input1, double* input2, int length) { - for (int i = 0; i < length; i++) { - output[i] = input1[i] + input2[i]; - } -} - -void lmsFilterResponse(double* output, double* noisy_sig, double* clean_sig, double mu, int filterSize, int length) { - double w[32] = {0}; - for (int n = 0; n < length; n++) { - double y = 0; - for (int i = 0; i < filterSize; i++) { - if (n - i >= 0) { - y += w[i] * noisy_sig[n - i]; - } - } - double e = clean_sig[n] - y; - for (int i = 0; i < filterSize; i++) { - if (n - i >= 0) { - w[i] += mu * e * noisy_sig[n - i]; - } - } - output[n] = y; - } -} - -int main() { - int length = 100; - double fs = 8000; - double t[100]; - getRangeOfVector(t, 0, length, 0.000125); - - double f_sig = 500; - double pi = 3.14159265359; - double getMultiplier = 2 * pi * f_sig; - - double getSinDuration[100]; - gain(getSinDuration, t, getMultiplier, length); - - double clean_sig[100]; - sine(clean_sig, getSinDuration, length); - - double f_noise = 3000; - double getNoiseMultiplier = 2 * pi * f_noise; - - double getNoiseSinDuration[100]; - gain(getNoiseSinDuration, t, getNoiseMultiplier, length); - - double noise[100]; - sine(noise, getNoiseSinDuration, length); - - double noise1[100]; - gain(noise1, noise, 0.5, length); - - double noisy_sig[100]; - add(noisy_sig, clean_sig, noise1, length); - - double mu = 0.01; - int filterSize = 32; - double y[100]; - lmsFilterResponse(y, noisy_sig, clean_sig, mu, filterSize, length); - - double sol[100]; - gain(sol, y, 10, length); - - for (int i = 0; i < length; i++) { - printf("%f\n", sol[i]); - } - - return 0; -} diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Output/NoOfLinesInC.txt b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Output/NoOfLinesInC.txt deleted file mode 100644 index c033b33ae0b5..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Output/NoOfLinesInC.txt +++ /dev/null @@ -1,6 +0,0 @@ -audioCompression.c: 89 -energyOfSignal.c: 62 -lowPassFIRFilterDesign.c: 42 -lowPassFull1.c: 89 -noisecancelling.c: 73 -periodogram2Conv.c: 64 diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Output/NoOfLinesInPy.txt b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Output/NoOfLinesInPy.txt deleted file mode 100644 index 2e215af6f463..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Output/NoOfLinesInPy.txt +++ /dev/null @@ -1,6 +0,0 @@ -audioCompression.py: 19 -energyOfSignal.py: 10 -lowPassFIRFilterDesign1.py: 12 -lowPassFull1.py: 20 -noisecancelling.py: 19 -periodogram2Conv.py: 11 diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Output/OpsNameDump.txt b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Output/OpsNameDump.txt deleted file mode 100644 index 7cbda332fad6..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/Output/OpsNameDump.txt +++ /dev/null @@ -1 +0,0 @@ -constant, add, cast, func, generic_call, mul, div, print, reshape, return, transpose, delay, gain, sub, zeroCrossCount, FIRFilterResponse, slidingWindowAvg, downsampling, upsampling, lowPassFilter, highPassFilter, fft1d, ifft1d, hamming, dct, filter, sum, sin, cos, square, fft1dreal, fft1dimg, sinc, getElemAtIndx, setElemAtIndx, lowPassFIRFilter, lmsFilter, highPassFIRFilter, getRangeOfVector, FIRFilterHammingOptimized, highPassFIRHammingOptimizedOp, threshold, quantization, lmsFilterResponse, runLenEncoding, FIRFilterResSymmOptimized, len, reverseInput, padding, FIRFilterYSymmOptimized, fft1DRealSymm, fft1DimgConjSymm, \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PythonCodeRough.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PythonCodeRough.py deleted file mode 100644 index 1ae321552603..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/PythonCodeRough.py +++ /dev/null @@ -1,13 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -from scipy.signal import lfilter, freqz - -def TestNumpyFuncs(): - t1 = np.arange(0,10,2); - print(t1) - -TestNumpyFuncs() - -# if __name__=="main": -# if __name__=="PythonCodeRough": -# TestNumpyFuncs() \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/tokenCount.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/tokenCount.py deleted file mode 100644 index 3382e035fcb4..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/tokenCount.py +++ /dev/null @@ -1,139 +0,0 @@ -# Let's count the number of tokens in the provided answer. -answer = """ -Here is a list of 30 DSP algorithms or blocks commonly implemented in software rather than hardware, along with their input and output: - -1. Fast Fourier Transform (FFT) - - Input: Time-domain signal - - Output: Frequency-domain representation - -2. Inverse Fast Fourier Transform (IFFT) - - Input: Frequency-domain signal - - Output: Time-domain representation - -3. Finite Impulse Response (FIR) Filter - - Input: Input signal - - Output: Filtered signal - -4. Infinite Impulse Response (IIR) Filter - - Input: Input signal - - Output: Filtered signal - -5. Discrete Fourier Transform (DFT) - - Input: Time-domain signal - - Output: Frequency-domain representation - -6. Convolution - - Input: Two signals - - Output: Convolved signal - -7. Cross-Correlation - - Input: Two signals - - Output: Cross-correlation sequence - -8. Autocorrelation - - Input: Single signal - - Output: Autocorrelation sequence - -9. Spectrogram - - Input: Time-domain signal - - Output: Time-frequency representation - -10. Wavelet Transform - - Input: Time-domain signal - - Output: Time-scale representation - -11. Inverse Wavelet Transform - - Input: Time-scale representation - - Output: Time-domain signal - -12. Hilbert Transform - - Input: Real signal - - Output: Analytic signal - -13. Short-Time Fourier Transform (STFT) - - Input: Time-domain signal - - Output: Time-frequency representation - -14. Linear Predictive Coding (LPC) - - Input: Speech signal - - Output: Linear prediction coefficients - -15. Cepstral Analysis - - Input: Speech signal - - Output: Cepstral coefficients - -16. Adaptive Filtering - - Input: Input signal, desired signal - - Output: Filtered signal - -17. Phase Vocoder - - Input: Audio signal - - Output: Time-stretched or pitch-shifted audio - -18. Echo Cancellation - - Input: Input signal, echo signal - - Output: Echo-canceled signal - -19. Noise Reduction - - Input: Noisy signal - - Output: Cleaned signal - -20. Compression - - Input: Audio signal - - Output: Compressed signal - -21. Decompression - - Input: Compressed signal - - Output: Audio signal - -22. Pitch Detection - - Input: Audio signal - - Output: Pitch frequency - -23. Modulation - - Input: Baseband signal - - Output: Modulated signal - -24. Demodulation - - Input: Modulated signal - - Output: Baseband signal - -25. Channel Equalization - - Input: Received signal - - Output: Equalized signal - -26. Digital Down Conversion (DDC) - - Input: High-frequency signal - - Output: Baseband signal - -27. Digital Up Conversion (DUC) - - Input: Baseband signal - - Output: High-frequency signal - -28. Amplitude Modulation (AM) - - Input: Carrier signal, modulating signal - - Output: Amplitude modulated signal - -29. Frequency Modulation (FM) - - Input: Carrier signal, modulating signal - - Output: Frequency modulated signal - -30. Quantization - - Input: Continuous signal - - Output: Discrete signal - -These blocks and algorithms represent fundamental components of DSP that are frequently implemented in software to leverage the flexibility and processing power of general-purpose processors. -""" - -# Counting the number of tokens using the tiktoken library -import tiktoken - -# Initialize tokenizer -tokenizer = tiktoken.get_encoding("gpt-3.5-turbo") - -# Tokenize the text -tokens = tokenizer.encode(answer) - -# Get the number of tokens -num_tokens = len(tokens) -num_tokens diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/EnergyOfSignal.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/EnergyOfSignal.py index 2e6cd6fd2d18..56d974b5fd8d 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/EnergyOfSignal.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/EnergyOfSignal.py @@ -15,7 +15,7 @@ def main() { #size 10 # var a10 = [ 10,20,30,40,50,60,70,80,90,100]; - var input = getRangeOfVector(0, 100, 1); + var input = getRangeOfVector(0, 30000, 1); #calculate x[l] #calculate fft : fft1 = fft(conv1) var fft_real = fft1dreal(input); @@ -31,8 +31,8 @@ def main() { var res = sum1 / len1; print(res); - # var final1 = getElemAtIndx(fft_real , [6]); - # print(final1); + var final1 = getElemAtIndx(fft_real , [6]); + print(final1); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/ScriptForCases.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/ScriptForCases.py index c21d58de1049..1de7b78baa28 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/ScriptForCases.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/ScriptForCases.py @@ -3,25 +3,27 @@ import time # The script does the following -# Input : filename.py -# Output : TimeOfExecution for different IP sizes : +# Input : filename.py +# Output : TimeOfExecution for different IP sizes : # Steps to run: - # Open a terminal at the path of the script -- - # Run: python ScriptForCases.py #3.11 validated +# Open a terminal at the path of the script -- +# Run: python ScriptForCases.py #3.11 validated # Pseudo-code: - # Iterate for all the input-size & update the input value in file - # Update logic -- change the 2nd parameter of line: var c = getRangeOfVector(init , Count, StepSize) - # Run the respective commands on the file +# Iterate for all the input-size & update the input value in file +# Update logic -- change the 2nd parameter of line: var c = getRangeOfVector(init , Count, StepSize) +# Run the respective commands on the file # Path to the input file -# Apps = "noisecancelling.py" , "lowPassFull.py" , " audioCompression.py" , - # "back2backDelay.py" , "lowPassFIRFilterDesign.py" , -input_file_path = "noisecancelling.py" -BasePathForLLVM = "/mnt/sharedDrive/SourceCode/llvm-project/" -OutputScriptPath = "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/" +# Apps = "hearingAid.py" , "lowPassFull.py" , " audioCompression.py" , +# "back2backDelay.py" , "lowPassFIRFilterDesign.py" , EnergyOfSignal.py, periodogram2Conv1.py, audioEqualizer.py +input_file_path = "voiceActivityDetection.py" +BasePathForLLVM = "DSP_MLIR" +OutputScriptPath = ( + "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/" +) # OutputPath = BasePathForLLVM + "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/Output/" - +print(f"Running Application {input_file_path}") # Construct full output path OutputPath = os.path.join(BasePathForLLVM, OutputScriptPath, "Output") @@ -34,18 +36,18 @@ print(f"OutputPath: {OutputPath}") # exit() -# ************ Don't change unless u required +# ************ Don't change unless u required # Define the values dictionary inputValues = { - # "10": 10, - # "100": 100, - # "1K": 1000, - # "10K": 10000, - # "20K": 20000, - # "30K": 30000, - # "40K": 40000, - # "50K": 50000, - # "100K": 100000, + "10": 10, + "100": 100, + "1K": 1000, + "10K": 10000, + "20K": 20000, + "30K": 30000, + "40K": 40000, + "50K": 50000, + "100K": 100000, "1M": 1000000, "10M": 10000000, "20M": 20000000, @@ -60,39 +62,55 @@ # -------------------------------------------------- commands_base = [ # "./dsp1 lowPassFull.py -emit=mlir-affine", - f"./dsp1 {input_file_path} -emit=llvm", - # f"{BasePathForLLVM}build/bin/dsp1 {input_file_path} -emit=llvm", - "clang-17 -O0 file.ll -o fileexe -lm", + # f"./dsp1 {input_file_path} -emit=llvm", + f"{BasePathForLLVM}/build/bin/dsp1 {input_file_path} -emit=llvm", + # "clang-17 -O0 file.ll -o fileexe -lm", ] # Define the cases -cases = [ - {"affineOpt": False, "canonOpt": False, "suffix": "fileNoOpt.ll" , "exe" : "fileNoOptExe"}, - {"affineOpt": True, "canonOpt": False, "suffix": "fileAffineOpt.ll" , "exe" : "fileAffineOptExe"}, - {"affineOpt": True, "canonOpt": True, "suffix": "fileAffineCanonOpt.ll", "exe" : "fileAffineCanonOptExe"}, +cases = [ + # { + # "affineOpt": False, + # "canonOpt": False, + # "suffix": "fileNoOpt.ll", + # "exe": "fileNoOptExe", + # }, + { + "affineOpt": True, + "canonOpt": False, + "suffix": "fileAffineOpt.ll", + "exe": "fileAffineOptExe", + }, + { + "affineOpt": True, + "canonOpt": True, + "suffix": "fileAffineCanonOpt.ll", + "exe": "fileAffineCanonOptExe", + }, ] # Read the input file with open(input_file_path, "r") as file: lines = file.readlines() -print("",end="\t") +print("", end="\t") for case in cases: - print(f"{case['exe']}",end="\t") + print(f"{case['exe']}", end="\t") -# print("\n") +# print("\n") for key, value in inputValues.items(): # Update the specific line in the file # print("Updating for {}".format(value)) # print("\n") + value2 = 1/value print("\n{}".format(key), end="\t") with open(input_file_path, "w") as file: for line in lines: if line.strip().startswith("var input = getRangeOfVector("): # if line.strip().startswith("var N = "): # Replace the second parameter with the current value - updated_line = f"\tvar input = getRangeOfVector(0, {value}, 0.000125);\n" - # updated_line = f"\tvar input = getRangeOfVector(0, {value}, 1);\n" + # updated_line = f"\tvar input = getRangeOfVector(0, {value}, 0.000125);\n" + updated_line = f"\tvar input = getRangeOfVector(0, {value}, 1);\n" # updated_line = f" var N = {value + 1} ;\n" file.write(updated_line) else: @@ -107,39 +125,44 @@ if case["canonOpt"]: command_llvm += " -canonOpt" # command_llvm += f" 2> {case['suffix']}" #OutputPath - command_llvm += f" 2> {OutputPath}/{case['suffix']}" #OutputPath + command_llvm += f" 2> {OutputPath}/{case['suffix']}" # OutputPath commands = [ command_llvm, # f"clang-17 -O0 {case['suffix']} -o fileexe -lm", - f"clang-17 -O0 {OutputPath}/{case['suffix']} -o {OutputPath}/{case['exe']} -lm", - ] + f"clang-17 -O3 {OutputPath}/{case['suffix']} -o {OutputPath}/{case['exe']} -lm", + ] # print(case,end="\n") # print("\n") - - # Iterate over each value and perform the necessary operations + + # Iterate over each value and perform the necessary operations for command in commands: # Run the commands for the current case - result = subprocess.run(command, shell=True, capture_output=True, text=True) - + result = subprocess.run(command, shell=True, capture_output=True, text=True) + sum_exe_time = 0 - for i in range(0,NoOfIterations): + for i in range(0, NoOfIterations): # for command in commands: # # print("running command {}".format(command)) # # os.system(command) # result = subprocess.run(command, shell=True, capture_output=True, text=True) - + # Clear the cache to minimize caching effects # subprocess.run("sync; echo 3 > /proc/sys/vm/drop_caches", shell=True) try: - process = subprocess.run("sudo sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches'", shell=True, check=True) + process = subprocess.run( + "sudo sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches'", + shell=True, + check=True, + ) # process.wait() - except subprocess.CalledProcessError: + except subprocess.CalledProcessError as exc: + print(exc) process.terminate() # The command to be executed # command2 = "./fileexe" # Limit execution to a single core - # command2 = "taskset -c 0 ./fileexe" + # command2 = "taskset -c 0 ./fileexe" # command2 = f"taskset -c 0 ./{case['exe']}" #{OutputPath} command2 = f"taskset -c 0 ./Output/{case['exe']}" @@ -148,12 +171,19 @@ # Execute the command try: - subprocess.run(command2, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + subprocess.run( + command2, + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + ) # subprocess.run(command2, shell=True) except subprocess.CalledProcessError as exc: - print(f"Process failed because did not return a successful return code. " - f"Returned {exc.returncode}\n{exc}") - + print( + f"Process failed because did not return a successful return code. " + f"Returned {exc.returncode}\n{exc}" + ) # Record the end time end_time = time.time() diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioCompression.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioCompression.py index 0b190a877c9a..489044fe81c4 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioCompression.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioCompression.py @@ -9,7 +9,7 @@ def main() { # var a10 = [ 3.2, 1.5, 0.8, 2.9, 4.5,10 , 0,5,5.5, 1.1]; # var a10 = getRangeOfVector(3.2, 10, 1); - var input = getRangeOfVector(0, 20000, 10); + var input = getRangeOfVector(0, 50000, 1); var nlevels = 16; #powerOf2 var min = 0; var max = 8; diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioEqualizer.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioEqualizer.py index 0dcea71954f9..209734bc6020 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioEqualizer.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioEqualizer.py @@ -5,7 +5,7 @@ def main() { # var input = [1,2,3,4,5]; - var input = getRangeOfVector(0, 5000000, 1); + var input = getRangeOfVector(0, 100000000, 1); var pi = 3.14159265359; var fc = 300; var Fs = 8000; @@ -32,15 +32,13 @@ def main() { var lpf2 = lowPassFIRFilter(wc2, N); var lpf2_w = lpf2 * hamming(N); # var bpf = lpf2 - lpf; - var bpf_w = lpf2_w - lpf_w; + var bpf_w = sub(lpf2_w,lpf_w); var FIRfilterResponseForBpf = FIRFilterResponse(input, bpf_w); var gainWithBpf = gain(FIRfilterResponseForBpf , gainForTreble); - - var final_audio = gainWithLpf + gainWithHpf + gainWithBpf ; var final1 = getElemAtIndx(final_audio , [3]); print(final1); - # print(final_audio); + print(final_audio); } diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/dsp1 b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/dsp1 deleted file mode 100755 index 4c6877a9a9c099e274074b416d0137cd6aced1a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1320360 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

QZ&zW!m2}LgXd`!nYoxsita8N{YFL{=mhx z>4O=BQUgcg4zqt@6BnX?uDPeog`dOP;7gT8i<;1oAW<(heV4V&lO7E0m$(m@W5fGoax3Jpk1d zy%;qijNO(c*q5yEPNpujs)zYY5y%Gfhu{2LgZWvpqK(+bW`Aa;V|*!i_&j@9V|+(WN959$j6 zuAY~PBkna^j5M*1Br&SE*JD-%F7bP)o;$07G~m|vc=7BshsS`wB8h`+Cx1pKJsvB- zT+oV`#OE#loT&IMI44@Yli{%gH25}N2~PyiLFiaci!zEnw*>$MBjXJiTOu@kMu~aS z!Wo(iaicO2YyH?T#2PCj4l=h{gO>VE<68)FjUWG0IdVJ<`N15y!YgSY&x5%Qyw6MLDBPgwCn5js{8A8||?vscF@s{?;>aNg!5I6&jURMy#Rw6_mSBIv)rDgn(k z8-WuR@#0~r!r*-7T)kiv5D#mtGBbUc?lo)Ek)kjJ4jShsEoiD#EVO^OgYj0~V;{lV8g`D%LchS9||D(eFr@kpJh6+j>}!w= zy)lhgwMmcdc{YD4sSa-qG(C9Olom<(8T zw$lO!GwlZbW~_I~2wf2m5Q^=c?KHj!3kRZd+(WH(6;rZ6agJ7UFtJ?ftTA1*-e19i zh%iQp?=7Yan)f>|n0ybIYPD5O0UqxsA)anPrj=BkhqY5VlqJ|B(O2%0(d(|D%1s{-Cyeo?$zc_Cl?B1Vjqg^G9kNv!gaNaJ}@ zX#LTo6U(p!ZM@ss$_)y3dqgDhCg)jjPvZ)%87KpJuws=HoqUwv{*@?AWaR7%O;`_6$-`(0w z)f1HRbZ4%@hsflo_>v<}*GLjHeu?4nSL_W_;nxsqwCLl;rQCzywh(On1)aRTm#Sn0 z&E;40b&L76p^=O)%~|}pXvo+-_A_fDjFU3T0#dXr&?-5$nHR`(dN>z{uLQZ8plok2iyC+w9r zcJHNHmCmz!&CqJx)gxy*aweL&l3Wb42324CZnUm2jNsHFCjHnJZS30{HP0~!-$(YX z==L#oY*iO6k~^&Lmo3eK{xx=bVhUs|r$W~8z-(3AeJdzv3#4cKu0{`L8d{&w*w>Blw^$*K+1 z8@N4{cllZ^ciRnbhT`0HY=cyhgHS*%&**8V(L62G(;wJo(DE0SthDlM;;Y2cXEP}r zibvM7eT`8CJ((Hcbo${eK!I8tG4QuGJAaW@mB9CWNzEd|%;D2#OB644R&G>sdsE?$ z;17|jREPW%8|jrK{2wVL4+5UdtS0DzIQ2Av(v3_fL$H9g>P*dsyJ=JpEh#@s3s_%b zR692bgvJ);srJ~1`>K{%;rBpa-vEia3qy2@)uZ!!E&9GS1nSXmYMDEgO=`aU(^{LQ z`%|G2=d6FwPI_258VIQR#QHBM%tP16%zL4@=)Cenh^f*!K6>1RDAl8immbx%Jl1%0 zdAG+;pwCWU!@PNAOCmFmr|jeBo1*0-8^(=HO+>{XUCzpG*I4S*a-O|*PL0ewoxFHa zN>1UxRBYafM_)K@-}5I-h^3Ai7U|hp31e>gx;4`Cs4_iIu+QaszL5faAE@V2`&{L< zf;TGMN>OCyZxKw7mh)$1YD;yXGm7JPLupa8$1rMV9JC;U(S%>W;lurNd3U;qW`apO z1h(E8#vo;#zlRoU)yKU?I}oinai5oRNqy+<$Ho*hTZJ^yX0|0~;TI*i#-23nd=Nuq zG+d^~(@CwiRRxOfVljVam&f=F*teY}eYchL9SqcoVnk>)OR8wQ<b}cG`4U@JL*UL2ZU!3jB$U*^KM4n^iCd-M zjnq0v3PZEukHAq)(Oc2R#W?X-v@FIyX8j88t@MQMeNk;ri>RbG*QF>NK?!`)v>wP#8mq$V3G&8rABhAa9t=?X=p!BzdH9>rEM8rv#fDKT|6 ztF&-fJ3DL;&+wF0T2fNVSPgchv;>Dv`cjt^Uz3t(W1jh)?Ja0peWtoqB*r3;Z42e# z{FzWr-(U?G4NIF3sX`Q`DyB{8&L$q+o8Du$I~}9dkP7Fbr%M@|jf~Ab#&Fq);WC%) zeKGlM{)96pTJ$20ikiL)Tb2gvE3MAPa}X(qGC*b%P<48;^`{|;t?=~*BM`p$C#~J; zkDGJwKP81fv3|8mUaVUHUK?i6ztnmE2C0Gx8?U}p>k@bGJ_kQGg_WefQHOp&n zVp-^X@Bl_o>O}MF=F>y-%R|=EyU_gi8|Rljl$ngB?UwwV1a^BR=O{5#a$eQftzKkI>w$wt40(wHNq?SnozTn?CO^Zv zF~Y<$dR#^d#pK4a44)@(7aCky>FbMGT~%hY$Mfyrd$@I2n>Do}B4^6OeYQTo`GTBNZ86 zo%%RB;OGPUFJI_(!NQ((_XnUfGrU#HT4u@Q`(3cQ@MGylZ8O1WEyJYDU9dXvRe@)> znLKQ;ig9`nBN0$UeY*=*b*Xi=mDD;{yRjKF$b z5vandpsP#1R2QlFgxw;U;uAC7|17A5)I+fGQp-~>8kBDEQ2K{et*x13z~~fPMK3bu`E39-&<-i_KhGBV1;_YEuvP06v7!ex7OKug2W{JSyq!=V{_m zYlW&owz$SpO2h8waaxgX?!Iq|`NveSN-?IQpXlk#6^vDSQmM0kolKlMUQWV^bSNti zGRIzuBv$GK7C?$mt);^qh}xzhd_h1psa0{=4!qS!#ouk_ymhH1DJRARt~RqXK8OR% z7EEIb-6Z51Q)q!|)*XO*V2@N6eH3kcvZ%W6zXE+bAF<9j9CX`2wjiJ50{<|?A!m2Z z*PjmJYb{?ro85g7zvO%>SCOMCN!(kBsb=?dC7fmxZeg>svu&u<+awx&beh#ay(Fn* zvnGys8|k29^}hVi#oX?%dYJq3N9|ya(|6x1<_;{SK7%=yUab+SKnuB;t5zO?zYdXD z#}Z+r`Z@PTsS(+i#wt8bo)I$^Y`E7Gw^F_d3mXotUg0k1S@Wy7hO@e&DvnoUu(L1S z6hp8nk8hf?P6TzLuz^VZ!EF4-IL;jOhy8*(a^fl~->7f0Vts1yZa72XtcM;|6V;JV zdeN-w>86~YsGliwQ;yLo{k@xVCdqWpxhaQb9Shtaq`Nb_+<6&R4G_^}7w30g3j^Kd zxHT}Hp!#Ws=udl@vDQVOU!rD2^tyMJWqA?(Vng)LA>S;q>;50RbCb5Aj>Zd6WOgPGm&a`z2++zA=T^}v zNZUX=#y|6h9BQGCjCslH$e{K*LZHTP?g%#}b=Z;91D5UY6W+aisxAt5q{^ea$=J?5 z-L>LC3j5bv4gJ;8O+#BEtJgmC$VwFT7oGg{DCz?a9vF%`hkdEutxFUrNKAK9iBCd? zM$L^&IQk0Hk?1Rjjs(a!)sfld9JbL0By_P4oLeEtQmeR&D>CbsMwAp+G3s2m&8NS?6macOnOh?*>xypC}weuXm*pOHqA_-@{=i-i-v5CODBzs^@Cz? zyOk{@aURkL_g)j*o{Up3E~j2kJ2jL~dq+`_@#$?~whoWh4=#EVH)D0*=USdYo&rje zvmA8!Z`utjjTlFg%@r0jhcWL~A+qFnC`yoRF_p1Yv@FV{odb*9GhgW#h-s{JS|D!K z6F?lA1;YBl1jpYDwf~!N+>Mj0F17kw^g|X2VNLNC+xv!>xx4f1v2&GEx~9Y&KO3vb zg2%}_pC?W>ER2Gf)88(z8_^!t2&+EGW#TGr_mV!iNVre2t$cx{E902My4+FUx%@YK zb6T9E4!<$g80S(K=RRKCcd*aRNDq1{^|~GCy~J`T8Yv_gk@@V?`ERy%@;-TgyQ3p2_}(- z3=&bO-V7P$)fPN!9mR;ITge9oh)dKh}_(1he^u6B(XB?V9ed9$V^HO0aNk04_`R*Cw>nT~?q5H- zHnpKP^|e+$8`loIfXxNwf<3ML#n>{f>rpwH7;7%B0Hs)$)%LiN9{TuS%Jsm9!TyJS z{=-23p-K6^%N$t6ChRUw+OR4O2Zt&fSaVpyp5WEdGZ#B= zt}N+f4gz|A1pngD2fYt!%6`$ikw@OALhtcZwt&*}-?Oz@e05Y`etIn3IhN{bYf=5E z_0Ex=UHLf%p?_Rie*O9R^Xtpc-%)zxk)iv>QfRMjIH9FWqjW?|=d;rFU8`E(4Pmcv z|GsN`9l`UzJw3m_`R`0!yX}p6@)8M!=qW${=Jq$Ap7Y-|zKm=zs?}Yq0uTgW+w@`I zl%6{Io6D2_jpdtZUz~2hEYLpb!@en<`eQlp<>N2^J;)3t`|bTe@0P9qU(jbq%=sHK zej9!IpGLp)%Ci_!|AfEoS^eE2(sN6GPGAxE#InV9-^Iwt`X-L<>Bycz+qc0-N9)P? z`0wcbKMlY6{rLg@Tz>vRzUAU0pI-9c|I_H_KMmd=@SRJKKk#QRKJw`!|NTFW-sk-< zv!DLc@%g_U{(SoU;XT*>{Q)2UyYW%Wxrx;0>;{gua+^il{j@fnM8tNX^n88x!+Ta@ z{eZ8w^z{FlKG?sTS-}~B#wOweb#qgH3T-LZ_>ex9^iyKoe_-=C7m|mNHII*gbI)?O z|DT|Q$q%z|ilvu1$M1f5A90L=_@CL4w>C~r{&H$_pZ@mwWk(2l4BVb)5gXaQ;3W<cAWnn z!kY#8SMJ{-e$EQRb8koa!Ox};JX1T)f6Cb*cw!ypPxpRK*P{6S?bC7oUxo8KgF5W* z*l_-N9p^thod4>M^Y;knKds~Zt8|Tp-`{>6=l@+e|HgqG_IFx1|FVwqXL_IVXlT4{ z=_o(+AFh8?$N4|f)fRq#2X~Y|)BD03LhzJ!lt0}&6Rv;dejVV4{wm(Q56`_Fz~?j{(tod^*7dW{^!E^`*f5))4MlwgaCeL-wyhl?maeK|GbX#A0Ezsbw~M;4`+nn zIjy7o>E5e!6OE7mejVjUUq2G6f8#zK^f%Kx`EV%zvX1kYoD#}^OUL&D|BV>}Vm&x8P5@9eF&N{4Vmnale) zSKh4{VJ4FB9wyrmf+Ta%UWOBgM3m+dvIpjpp96!ZWse7L#qq#V_8Owa&H~c2-XzZ+ zA}na*TB4g?NhAnP#4dGKU_&kC@FEf2PS#Cns-8;AsHtDBg=Fou{PH(%X8GZpS$@0C zEWhI3&FueXmLI;E<+t0+@+*G3nf>3)^20Z?{C1mJe#LJ#v;Uh}e)wjV-)=L@uefJ3 z`@fmxhi_*2?KZRg3gql&$-m7kKYTOGZ?~D{SKPIk{ol;;!#A`1cAHs##hshk|II8v zd^5{$x0&Tv{CYF{znSHSZ)W-bL)@3ZM^R+|CnN*IWd}Koikg*i4a$)yC|M8_2-1TS zh;oV?aY1Aim6ZvgAc8ZIG|ecA2kxS-;w`SnDgq)T1PCA?hnyZDsMHWJ2ue7F{J*cN zr>E!WIh_BG`1wetyQ=Gb->X-zUR|$RCHVagcu5Jh{{+7ul;HQR68wJ0D+%%+=l3+3 z4%gG6lzZ>Prv?YrHy@~{-x?+T+9>Ibqoh~89GU)?QPSUwlCDNczb{Jqtx?jijgsCt zN_y2xQTUIN{$7-HHA?z@QPOXXl74NJ^u|%rtL8`HKT7(0QPR~Y>GwrRztt?=yX77r z_qP{s-ru!n8h!qO5nh(8-ViY8%%tEo`xn!NIb$%!%V6gaQ=JBSnf}ou1(!iUNFH3~ zv1bBgL(P=v#a{l)k;}g#(ek@TE`P<-3F80$UlHXqGSTvtu)@$g%7F3A+;o z`HMHS5A-+Qz8V#OhojNQ_uf28r02W=qBzlt(gK>0HM@*u5(%^#YhbHM(^`%FDZvUS zqLq&jRQq3`*V;3`iOTyGf8_^*U%qtOMeO~+G#zlCF_!0jeV$)_d4p&MYG8lJCT;q( z4wzkPfjjRUHtWOF)CcnETCj-@b&QN_qwN%T9ou_#Mc(-6ef znJ=q-5Lb1bQiF|KtjeirqT=DQzT+u!TY_MDbYD!~mcwN=}MShzeqmW@lOR>k_{ZlhGR^!Jf& zT%Y_Dd1!r-!f_4vPV(qAqUC?oP@WQ|yx~FR-5pTg)j{RC4yQXj7P`bFP*-rB0R)v> zM1oLdYwh^7+q1ViJPzAM%!UGo-a*rCq(v=we6eB+1e9LEtp4IPeZ*{{Ls<_7e|IQr zFsJw>S@!^1WZFGbZE39Qb2Md$i?{b6P8n;DCr(6GJ3$92v^3$xvzu_0=FsG#z9pgW@(;w!BqkxZlLA|V zf;D-D)z4-3poPhfbL0Z(WqdK@9LQs|ekU~+YMy;EHc&(2mp^2qp?$(;C_QULJ zeuDFLxJp$DZW$I@%h;53KlR%+UIk6bIg zywZ`gDNmYPo^M?*x!3X!PvN>AGEazfeS!4EulR*9k_aN~+J6-B46(O9wK4u_ae>sV zMPsR1>IJf^9QC-R7?atEF}?q&!_%a>oV>%XY;`E>>=c^33KA<&D(r2y***6mnBm#_ z6SL*y(+KoAEm=<9gNw-iVCr6R#X32l*Jp#+QFwX!}EEv17j1xP^TxZCPe z0==wXI;ehMPm>e}n&*ph4PrGS^7}#{q=k>RJN-2@djtYR+R;-wACr+E&Dntpe~q(#jU@*W?rNiMy$wscUfJ!g0ELGM5cxVdOt3QA_lQi+O+&Mm!` zw|-O>qS$6{#T{aHWw*Bwa1kW6rz9)qph2~C%bx4x<`~KdqCcX0218FTepap0m05C%baW?)u?qUr%b|KAsj09Yl1qBj+p0Jqgep zZNK8=9;4`&^(S8GP#f9RE(HqO`rl;bm{5{!a`I;0VtR3AnoEnaa!ao-O_M$SlOZB; z-MqA@f3mb_d~$B-gyhn~WTe&OY0daI{p%sE9@6TS7UCYK{wX}|cd(4_(0a$GAT6b| zFa>F;JZ-l~OGR2L(o#zc(Wv`dc-j{t&4M%w(k!Kg7Np@g53sx{(&|f#L#Jj!hch!zL&1EA9lKTW|KV zMQ}Xn@@(sL+*F#`A3b z;wE`By4gnEHEx{qs_e2-3Y>|~4Fexs*a$MRwhZRqe>5g<##CGC-FFvn=_M^Xf;pcq z$@cP}Qv0a+*XGi?R~FxlS@o%}4~vTDn^FTFi@-DSu5raN;>m-AQ|~U`77@*7J~Y1Z zmtkoUx{f;HmBl39Z=JRjTW56P7@9!_+~b1q-KeIdr0J7E%?nYE>X27`kice-0n|w0+EBMf5b_$$?$ZQp7kIU>G{xeW!lLKUS53G@o5t11_>oQwf zqRVU~`PQ#hQdY1?sI zRXbS8W13D;{bCpb42I1cm;?|pgbxOWnU$%hWV_Krg&){;^p&cst5&}lsD~%;6RId2 zjbfrfCJppnBm;}l#kccDdY9}=w_8FylEScE#aOZF;jG|^^1PTthy(D_ukQfiWQ4#3%7W5Xs6@lKUIO!dq zYR=zIO7mFcNc4^)F&_S05$N3`=Ay&DC+x3TNM zRA2hXN$cByQ~u@xOg!>z6ML2q&-dTrvQ_rFQz{JlVF9*Z>+z5gLG9{zep zpm(*PHxIiIOy$`#PI}uWn)CM=rJ?_eL~k1sCL4wP1?gG zL67}A0)KM>CLVdVi9oM`pf@=Zy*6>u``>tT{$8Lo^na1){SS%p@Ygc}y{iSidH;$a z&z^D8+xDP2f1gnr`oBo@wjnVd{zgTh*IUqA{9*+DM#V|*ICgKC>E}*LL;n|v-f<+x z!=Ebxy?X?`UGpOF=ZceF^9b~6?&ti`J_FPKsCk_97EqZc?R}=8ck+b@^cDb2Jo3Cg z0=$h?X7nqO9!x(aPWp3+hFSd!`s^7q{=?9B0c>3SN1$JGkKo^oekIaF_>YtR zT%uvdzo5^aHse1GeHXyS#eW3)HDd(-X7nqO9>RZ|^yd-{GyVmAHph(rF!Wsj8yEi( z=-1pW_&1|piS!WuV@E<4rxkST^ ze?gx;X~usT`YwQti~k7pYeotF&FEJmJ%s-_>CYt^X8a5K%wxuX82T=Njf?*X^lSbu z_&1|piS!WuV@E<4rxkST^e?gzA zX8ebt?*iDk_>VxprbzH_M!yp2A^gWle=gB5<6qEcv&{GpL*E6kaq%C4e$7b1zZv~X zq=)byC;hoZ!;F7HpDAYihoSER*tqzQK)+^$;NOgXCDKFqkCXmfqG86rpwCLp_zy$h z1+a1PAAx?&aKXPB{Ys>V@E<4rxkST^e?gzkG~+)EeHXyS#eW3)HNyn|X7nqO9>RZ| z^yd-{GyVmA_JkS#Vd%R6HZJ}n(66~u@NY)H66qoQ$4P%K(JsNmm>ekIaF_>YtRT%uvdzo5_DX8ebt?*iDk_>Vxp<_^KX8U0G6hwvXK{kcTL zjDJC&xy<+vL*E6kaq%C4e$5cUzZv~Xq=)byC;hoZ!;F7HpFM8Ie;E2MfQ^g)2=r?P z3;xaMS0X)x|2XN-B^qY@3;Jw^8UJDEy8t#W{v*(@86@~OqhE>i5dPz&KbL5j@h|AJ z>1Ou*w{}Jfd^cVb_(XT{$2>)@?pG!2%_!soqqh|aE z(H}$y67@5+MFCrN5y*}21@;WHW%#GT9iGuRr?`UJfZ8(m)S21>)rRwz!)UKDPA1SE z==6e;AD#b{aqa*|4So=(Z#8(T9KIUDNLp!abg#n)SX17nmtV(C{8C}ckC)TXD@=qJ$5_22?!P^ zW9q#K=Qs7)ci%p4`mk5_k5#M1Jvn`$G1LW23w*Rm4`VbQG%j-%MF66~e zY6pe~#PLwei{FB}ascPCWY<~*D@HZb0h?NEou;4swB<=BHc_vZx14qTl>Bz8^MYLW z5y~&+ceB;ro`xsq<&>Mw_@;`j;cZaiUHB=j>5w28h9dE^8x&ZOT?UP<+Q^OZI&~A5P6&i2dJj#b11v92GtO;zI7W_~I{~Lyc+i7Z>nsA@LX60YOlc3W&eh zEeIi(XI%Ii1>kEEfNz8!UrKm+Z*Q zfM*NAH}2NR@|%kE#N^i}0G}fO-^*xyn*4f($G4$RWPINuJu!Umk~5^sZy`PD@n_FL zyES|Zc(xGvwTptUTf+Dn1>kEEfNz8!UrKm51W6PfmXD$9K6OUj@$=g3l2J->`)7T@!$>T>!pmXnmUe z+Jwiquy}GFqP|zng0EY`_!JX;7pM-+U+62^B;0KRqs z_@<%tY4U3m9^b+qk>$4x>50j27I{dz{ASaWF2C=g-5Nd@&lV!TCQ51Wcmpmk0 zehcYIm)|*Pw}x*4&lV!Tc2V$kOBi3H0DMgX@Qv`}O9_u}_RW#yHy`PV$?snBkaYQt zqbFT{%c0$x{6_I?A@V!iJu<#Oke(R6_2hx*_%_g!j_-0mz6zc#1fL@czF`UDyCwi% zy8wLC(E2p_wF!@J;Z2d{w+!it$!`{UNV@!H(~~a0@1flqJ{QjxBEKe4@U>1D-y!lq zbbN>DNypdSkM97_7J_eFH{_3G|B#-T{Q3moa|Gaf8Ldx~U(fLPHgt`Q?^~oNhVNbS zkaYPiq$gc|=b+shz6Cs6i2T|`!PhNee2oI|H3`5s!jCT{Jigg~i!8tSNKZ_D_mYRC z%WoV#>GE3+?bhTsif0Rv-{G9d`2Ik8V))jR2cqNKKuGC@V?bh%u;Mqdt*DeaaZej3cm(|?Tj^d#@5xejfgi9UN5i6(V`$eqO zty+}Sv|M!%MMR{D5;xyd{~MnH0`KeJDj8r!ouot#BHjVU7LM^y$3nsOwW>3 zzCPHp=5bmdTvNMU?P<%drKr(}tay}%UuM&hHTwjGjhl#HbP@=cgU&1AA1p!WREUKk zU>l-v`Tz5+k6>w(FN$Gh%@7z0L9Argx#VKRUpkfSY(t9Wk6tB}?4dGorIxm^l{dU% z4$Jw=7s>)ZWvM*c9|6o<_8H!xx$l32~BIoQ`irNnSF<^I?=F+vo$(40?|SH-k#{+r56L+xl6wj9x%i>GC-`6Oz$+f!F_Y+K(4r-R zNNKT(MI0~J*`~!k)r$OrDa|86;_d2tDSnjY{e3eY|UAn#pV zYWaw-IK?_aD!B=db~V)&@C70-YhO6Z&t~0!y1>TplGq^zhuRQ zw_ly%-QJnw?H(WA7Z%r*e6JFCpX?Zyd~aM*S9ptIYH9yNhq&;rSX5VdS6|QZc8Cve z@6YNAZ%cu9TKl-65y`I}nB!@?CySTzG5h4DTB-yuf=we0VEAt}FTe4nqdKBihC#->d6PzP$wA|7OO8 zx3A!V__bP$+Nn2dECS7J>JH zjJV|cV4dL|2*V5cF25o!yf@Yv-Zx-)f%k&=@b-JRuJqw|7&71;ad}+w?e}(F;q4{x z{`a!D@b0WLywAb#LcZr($A$Nvx9Up1Kf#azZ{PUv9$ZjYcsmQcFJBs$d`G@fS9nWc zcp=|kE{O~86|dJ7-t91Cz}r1Oyff=eAFdL3pS(Ce`PLcUVi;b?_xtp?@K*n)uJmCw z3@`9@h!5}HSLzCHOM!RVMRCcuL7nNt-7vh6@1|C9;jMhRuH^eE3@`9r9v|L=FVz)Z zi@^Ip%edtGL7m|p2*V5cE^iSIFJFxfJ#YN0xi7-q=tMEw}>DRa)h{Eooz0pFll@a5Ej{uKy(i!8Co@5%EozxQGIAiq|z;LEN9 zd`$(uiK#Kj@5MU6cRvg>>|wn<;^_*`0j^chWvJ%iveG99pKvt!v}mf#DXuo4(hkP!1w&w zSmalF{_EER!wmVI_#*~_K=toNv7M z!*B!dj+&V8jvPv7x_LxpA>V1FvgcF2U%*fT?_XoY>kPuXiA=!x#M@Nhop>T9`93u^ z2=C?R8}D!!Qpk7B?=j)+w>vpdA0C)-p5^;K3@h-qiV^RFuag&{Q$i7B@>R#TmImpXFidYlemnx zMpk7j!ag|E!gPl^BvVdaFQ*K`oADWrhUUo%+ z^g+3-q{%7$vgGpeR`T_QSx8z(uN2wpNEw`IM@h*R^ygGJ-KoLh2-|oV*EfIPA&Cfdl1Nv~Vax(&gmcj^s6tWDpsj zi2{(jUz#I%3w~zdqYQfOQ2TW$@U+MVaeMiClv$XCTuAPRFACGZheJuSds;UH=oWYy zBTEld+=)Wy$GS1nxN@mkZf1pu(IMg^h>I#lLL?pK@&l=Iauu$Ea9&JtE!0P?vTJo3 zoA7HYf?j-A{EJ;}XS2Ijr8#nbayGjYaW4?^!#fbEJgx-Yi=Y+F3Fzgc9*!flMQHqw zk`qqc?@+_`qYm>_hpjb*>PX9@01|c{Y#+BkAj5&mR2#(@ad>iZt;5<$-H%HTrpTVu zT>QUMY6hVFQ7wgOc`kgI92iNY_W>P<#>@S2|phdxnP_c^UyPw6rdZEmWkqgQD7@j7IbhNHvRY)JSJT(3@%*C47JC zKJoo+^gY6s?1qiSUtCAPx)+^8bY4&qz49Cn0CMRPsH5OINt@A}J|v89*g@d-G@^h%X|mk60a^ zK32#XQDM}ZeP3D}%KEBuq^2P?jZ%N{rxx#bsD099&m*{VWSz4~f!ffX^ARU#k(JX` zvt-vwsLe{L4f5zr>Zc^!qdkxCw*bhREatKv$q(ppu@emw#$%p07C+sU~{ z8d(PqKy0#d#;*L106TW&s{&MiHLNQF+LX*h8eM1t75o_&ndH^(^y}bgu7l6tL^^bk zHT{E7a{Ua@!GjJ3dZFpyIOyQl=n)M%c-3t2{l+sCI;VyW1ZAQG5KD)jtF7wmt6v#G zhm_VfHX0dZYQx-G@cUs{k@gNs1&lxqoT+91MAhUeW!YuD z+-1cQERvPXk?;N(Jo4Z*^v)kXN!%|n0Q3erl-2xV`5A|kpyC0h1iQuV`+kFBKCPV!=$R9+VS0| zLmH1hZACJ&V(f^XXU5?y{Fc=l&`($cgqMl;u*2ZKNLGH3)%vn}p{(YnkAV?Ge_LIP zD_N%6ijthoNZ-bSv*H}oW*e|2t36>p#@VvSw5hpJ1pH5SmVHXu zQbE%!&Lx9Ak7P&jr-lk^O&l4l^>cWH*oc4Q#YMti(|C5QI^Q-O;0BO>7L6gyoPDcU?&ZUB#?^Z2f{lJ2utP^YdUei;y@lNEBP|0(12Ef-%$4HG^VdyrZz zDqS;};=<^1ZM~pXU#5Sd*N$w zgMl3A12#kagahr_cUu!;->pi=ZB6XAp9KM*JnhQYnn=G4l_!dJ|3XDqvf22n7C7w6 zYPA=ftHNYCXZ@t1vN{kg?PuQMIFxoPm)3(QUA^RFha#>iv$8hTD1cujhIRvmsAhSD z7Vr#nNe03sG?>~;|`*EhPwDWBYr zsrZLFNP12E7(Eybf!nqVAECi+L*FlZmf1d}GL$uJ0-o(^UyQgNwl3&9Y4B6%KxbCy zP#5xC4&5rGcu3=YGs@bQV`bae`PHdh(w(|>Bu zsrK?8QVW!??B%toc25h8dsa#_tC2(_8r~ye&f#DxMnQ7Ql=18;R2GUT@TA@hz!1 z&mTanG|XLxP@FI`GzWo675tJ~;U9Pdx4^#;U;EAYgYZ{KIYc+YU2)A%?v)Mi5dA%b z3#ZHOs!V35NE6iN5C;kOS|BLMIp}<%>RWyfo>I-PV5GWEmp%WRPACWO=iG9^d1=JN zR__zth3J0=7bul^g9?;Qc>{4rBDsfqvOWop7j=6fu$?Pb720bVwJs1MHvkSPcJPPji!&77r(j(Lj zeRw9h+_;#Md*bAJ=fd^AvYqJ3Ggt}gk2F4mU<^SmXjyPQP@<;6T7tLr>hr*Ar7^w_eG;3+jj2*Ti$6FF-dpk}tsZ zC9t0zgzBb#_9M38d#&OPcxlY+Ggkf4c@7DvaP+nf9H`24&GSe1m5X<|XjJh2sp}<^ z#VP=Z2E7@wr}cK!9aWwKVS_zQ;9c*MRZAZ?T$ru$8{!6nt6&;27dOF zPvVC|DU&_jE|F_?VKM~sT}@IP$v? zO0L;0m;aDr_oOzI+pf3goOF^~0lw)tI-$<^3C8b5w%-PsA7cp z8fuDYQ7vaU)KttLAqoEUQOuZnzeFph_U=etE9zTyu#Qw#bi~&XsKgwOW**VhAuFIYF?nCs9AE(^VDZJGlkEM@h)YB ze8hNHD*2L>Va6&do5WN>+JbhR2cDp5DnbfBO$8LMfPLa^UT9x0GA*Q}@Ovp_ac4Z+ zmCb!TEe=34%1+8T+lpIO%^EXDc2z)W8OqrvxhHXF44|%e9z@V#Tj63FK+7KLy!dPZ zH#}@IJQErd7iwc-^pl-!1G#@MhWam{31g^Vns1ZXnOo1*N{jHzdpU`J*f#;<=l0e+ z__siN(L3n*#=vKNbi`vN2QBaq;sT>0HeAeIleT z?EB$eq5MR~_Xzq1lK11(N5m}e0de8m0VxZ7j=1sdK~GEg_Tz8t_&(uU9Wc&|&_CXw z<6F2hRDLw0Z19VI*XBWk?mI?H?5IJ#Q9!jTYYZ*%ZeL5pLnv#)mu zzQ}$@)`xr1?~uG7qW&jleaP1!TK#=p1bMH4l!ZQY({TAl`~moA0?W#FQ#T4gKj3c& zppHJNlAr=zq>rfK375!BKHW7OOE<`2<{k(W?=OAx4?B5(@qaY`aNTyQAxtB7>~ z%wL$#KOFlymha8yA3l!hd-M5+hhqBPeE#9On7%ike>k=+CjQ|!1=&^K{7+2ZoBKx( z#q|9`)A@tzV*1{E{^8ixnD{rP|FQmkWc$zueGN6o+o{iq**@6Fs>Ix%y$M+e`_P)B z@>vJbJ|g3L7q*=6eM~kzW_)hVSC7qp)z|S&-9l3L{}*7t0$epr=*kMldyO=F7siF} zM|j_{%Wn*%Cgk_+<_P$NeSH|61PN>gbr!(_i(p@GBS?|$>-Ufj;aI8*h~$$ufM!Kj z^6eP|X@3UI{fo85+yIev$u5)HVkoo|W||Q0C38)a)kY6@IQ0OytD^-rMAC$S?&kWs zSL#%=q_%MC*#pFCfUi3no*>D!OJKj1tNFSZ3>fFDFVOgz&D>uJxpIE^_<0982^;eU zU?G_+s6(NdcZ~fEllowYHPV>5(8o+AB;=cUAMfkVgp<$>vtnW$g|DoVvq0mWEC+_9 z@EEYLOm0I4ahS=Trh<)b!-v#T19`g!6($?UpMm^!fVKdQcOng4Ufl#1Ie#(xKj_S< zo^HfnUQd70|Dj@xj2xsU14iQae^5)LfAD`c8tRL$mPTG58I9hUSQ&@EoRxUjM^ZQw z#$nZ8Lb-^xKJqfEfbc)XS{U$qZ$$NIEsE;Ldv(zBMM2NBerU{3>v-Jb$%F@LTx}8d zRog!iqP%>|&OvSZr;DthWgejhMXT@s1qoTDomsc1$z59(-(X3S+%k=(yS7|R4<)ma zLt0eWLEJEe(Kgh!ny=ZF9cmyg!nIEOQ&GujxuoG%wm@sDm<)mEv(o>Zy%iIo>{&|2 zUk7hZ)yx@`U4ItY@7A(k#@om+G#(X=hYDznH=G1KZ)#DQ%#OVL4dz`KjaQCIL(8O& z7R7m{cEZTxveb_8*^As0tCNef*sKje;^}-V7W_zIJ}*9tzgV!VWEn)%(|PLUBIQ-2 z(4+`f!1W1_VsetT28oHgT+CMuvt)n}CnHNs&)$qd^+jJ|9nnFRCgu-rL?>>n&#UR# zXJ;bRXXwP9m_iW~dZl2bwJObG590x=`0IEjin?9<&`rmsX8E=zY}tBVXJ@hHfvWJxgtN8Br8K1A={WR& zy$2JCN`0%f$glw!M5V&g`*v;64e8n}Uj-Yw4wW~OS03st{{CVQ5d@U|WSgn;Gxir_ zgZqo6srsNYOc(J0`mX@X>VeZ8)J3%OYb0NY(DqFP^n>*wC9+F1*wnS5{)L!NjwFhZzcMC+04Tn6DkbI|aAD40O7MUc{Z>-k`@1V%9mn(Am{0ZM8 z({^os7!rHsl-N~xmIQP zoE&}MfniaH-GW+0nN^GCtgwPDyYds;wi+hF(|PuA+_a~>N3+s$C6e$*ad9XfOYWuU z;}lmKrFks3AHnV;#YHpK_<+`K^Q1-f-DOUj!_&S1#n@?EisGUzKC;To@Mgnc`qr6h zST6c>Ft{0om0&s<-CFj#r56%OPv=b%*_Tust%X@9;~l#axoL3$Q`aXa(kqFj;+6Mavgj$@Y}qHOzF{j@p}gd^ zd<-Bbf5nNpPB)vlU-mq7M0OpzQ1(o|P|i6v;X`C!NOep9@%lB>pK6FToc_FYkxzdv zg8_=JKW61WWm5i!dih_#0Q~v#JDQa5yaf0gQY8#esQjeKR_D{C!ZI|{&4w!2Z#92%cq}7`7cxXO$q;_ z36)=sCKV3DY9JdERu{HN~3bEQF!wOI220At-h%8aLaH zp2?)xp;3!X6UE|gQv=#BptEe6d9iJx7Mq^rbO0?j7*Mg)j{xn)3sCIUX2nh#A4vez z>Enu3TveMxhCWT^tH_6zi{2m3ySD33DtV2FxmFeV_P5gBZJ(b8Fl-V07tE4WEuKT? zHmK!`^-NY!hK>)y_BzRRr}g8&^!C2={%}~sq&M}Y&kCP@WQA6KInvokKo{MH{x9_( z1L?X~8CSp>_E>tx)z4)gl!H~}m*H>+z5T@)NAx#q(aAL|zE)o9zX-OKhPPqX5_*Fn_r`l(4_KWcz)l0^O-p1+LHVOh56 z-mAl+OM^?r{VCe8qqSwBjbdUi1Kk-m58Qyy<&E_R z_;3MMfzMtZ40Y>d@*7xvP~xjq{xOg?t3GU5pbCikH1R(_fcAv5pMC4YzEJ~X-@&ns zZr>n}lh{a%X*k+>w+)-&$%8ZSBQ{{$`EIi;)`ig1h<61k6qoqGzv>c~+;i|+8{LLK zLQvY6%wsjHT}LA2#|t7JAsE^~ICpIMN>COb&=G>GuMy>ZhH@}mWjCY0#b4}ac;i}r zgy1hYLU5(n??FRoNPa51G=7fYPTrtnJ4aB7QKHr&Bi6?iXusj=V0HTXp zAEx7t6<>re-=2!{TTuB&Ab~$${z#MZ4_Q%uODf-!Q2Dh`?r`#XECc2Lh01TAQ2DQz zl;0x_<D_q4FOyDgSNKPp&`j zuS!HdSD2JPtSQPD`kzqwE6@(Z=}#u{~~eE3CBO-g257P*S93J=1B3ehuyAJhnt-9tl+ZJ0c(kzzVKVio+?GB(dA1mJ$$;!!lK!+AE zB@xlS-Ry@PFyGOYVvrj-Df~Hky3GyHt3$qw7OEwc;T~Ni; z2jvW$E19pA>vQ0QJU7!Z9c-mm)_8Blx4uODjVSN0KGRhsK;9#%y>feY7Ntd%w`qU! zph@|c)kFEby}p!C`I1Tb_mI8h?e(UF%Kre>8O|OIqyA9%Kk$(LynOnZl>Y(Ahuh0X z6Dq&@)A0Duk-g{r$z=(Z|CCAj^GQBj{$Idj`SbGWXi|O~z5L+`mH!nQQ@HxQlguQa ze>jV#`RDOJXj1;Sf1K9Je<`8zC6n^MIgRo!fc0iKB~<-Cb zAHw#fslBagza|*T_+ltriQVA6FiKBFN2di*7>L=7!ZcTJYcdR8+VBe5gJ=~whbGOrdjBDn{1*rJu$`I;)gu>Z z$4f+iOLI9m>)E3NZSgO}rfM4KV0%8sAKI4x48tg0#XLTJQOLeV*Qu?jWilF1(2-ze z8(W5-h%h0!X(RX26wq~ewBtJ-%iex8zWHSgI_~jwiUxDoys)d`XVG})G5XlG%r+h0 zCONxP-<~CVmht1W)Z2H#WOx=GMPx1c;MJECM~Ih+gRq{?pCn-dX5um$KvlC20R>(W zp}uwy(lK{yf3Id{62_5WOqF&hJkU* zl@oTQwkqB4PupEd5J2bACv2w8Qu+yCH8b%rai%#k;(1NH?E+$EcfR$@2!2 zIPEyAL}#dW(g7#z11qn!@PG`MVV`k06YvaUGB=k&c9{c6i;z@6C;V6Qs%Ws3YJ&E} zz!KxU-)gFq46>{|KEnVCNU~nUXTVt$oHG5Hto2wj)z~I;0u~2BgQybN?>K>`9a(_| zok~YLJHG=jgAMykPq;{YLi74o-*;c&J2ITB(G%k@#Ur`qVi9G5mPB=pExrfZN{j{e zDr4q2JnbOzS@?h(W6y8!7hC}K;+yWVAEiA89x={JEMrr7rHTk@gqynKxzw>6blw$4^l~@HyLQ- ze6WVtE~sG~e5XpUo%6P6+E~?qhd}K)T%#(^2;9CSl{`)r=hCxJQ$s9BqGY1oP z8)zP#zeiiPqHMmVOL%JMBnhhT@b*OEIH06(UBrmCK=BHY)S9d!OhJD(3q94)J75Q1 zUkv$+MP2s$o0^ow-V6Uh&Oi9%K)zA|MeIV#rFJ)n%FUZ@c9~tB$?aKJ%Q#%9BDqfx zS6wZG@!;^F&z0Oi;tiR~EYi**eroM!el`{Hxg{)2W0qp_>f{U4`ZQsL*RYF zp zlJF5JuNBUjro8vJ3L7Hi1xNx4Oacj1;5q1y-}xIvu!x(`AD zri}KSU!6_mobJW&A#Z_Nq}pghERIE1O096%56`5oS?m$=A-Sc67NoH$c%;*SWa00F zP0|8D(Uw21XUbadc>Y`S?>sl{&aou1uhFa06n6)t306FoLUh^MjsrRj>FIn&HPsZ_ zCMac*mUbzC0NC63mio0WM3;6S+|G9&P=BFqKj;U5`t}0{zP2(KedM`m7>L^yoT;@8 z#V-eilEbX^L?nuESSZo^CRy?nOq*lsXC=Glb$pn-miv2rUjkvFjR`*lZ%lX|`JdtY z-nsy$)R;^_I{)SOvAMk%fQcEJ9fPEAp8gm`lGp+<$ZBud;koMJUr)o8D_cV2N1Q_@ zQ%gtCX$p+}7hVaqW3+HAfY3?_;*5wAO0*J0v&4tI0DRp=|9U+C+KD~9DcQAZp$)DA zrr9PR%|5}d`Y(yJE77Hc&ioTk%j{F9VYNZ`2L7e=Tp1A#SfC+(G;Y!i4H**0)?x{| z0l1fv`NP~8taS?!Mkp8Mq|u%o>O|Q|G*Z})-Sqnp@q6QP7$Tl{M!Z zst?(-uL%Pak^WQs+o<#>8|Y6BravW!epf-iMJ)8C*yx{x@OA#hZv+40g`aTV>4(hiF)4sPO|xqCe<$ic!(adJ8~7K$4g8B2%QTZS zjF$UpN%?<5nO`SE0BeQ!Z1XmV<~jE8e+)fzME!V>mmO6;Lk#$a1>?KZhffQPb}4Y7 zetvr;hWe?7*@>Zj?Bdx(`}kJ;_RHrR@j};+f1ly>ow3k=FgE%_4D^Qu)4wx_{-uKc zZ!gEfKL#JM@Xxbp{EOcP{>2MF|M1Wue`hHDQ2l%`fWE1I4l&Rl7EJ$6AAQj!>iXFn zILIk7=w}wJJoIz-OR?(bSMSCopT#_zkk2yl+b^Hb#S2|NQxG=|@)@9kFtm?Q`Sc5* zZz`WI2KwEC>38+f50cMuc=kc^N!!fjvt)j(^7#P6pBVIK9?vG^Ghh7n%je(Xg)W~v zfeZ5Kq=7KVC$xX-7#sa`1O3*)^e+vf|4j|2|H;2&Xg?odED{USt{~I{XkpCzD3gJIgKR5(t|KdeR z|I!~gg7h=@E3TgxYtXn;7g0Z?0DR{D4+cvhCHx=p+mG*zc%jSZ^WV9Ao_&!Vw($NX z_s4vmr{zTl;no@5%E5>qphw2Eh<;i35sTZwKeUh&9O54~gMZHb!&WGO zKFfuRJ^5(Wra*reL%y>#{~h8VR=;7aKZAS_^@S$Cxqn#AcJXW^pKA84_`Q*S9~HlG zTn~Fs+9Av#ScM!vqO>1H+8gwn;dj+b?5=rGsfhJ8l7AG&ALtt3ugaoqUSAIJ8+i7z zBJo?`fvY;6T*XhB_k~#P*a$q%Le)m1O1{P`ZV66 z`hPBF{xPVCrT%#~jeqf5@GpM*`6pKl{KrgxWNh?h1AT{qeo+wp@q)hh*_ipqpduFj zc{YuI@mugOe*5_+R}lQiOn+o-^koBmhk<@k5dHCjzISfS{A1)23;#Tu#=rP2_!qzZ z{FAE({!QqI_NOBQ=o|XeK>_16*+Ac6pkL&pALQzd1rFF{Oe*R_wO)$=?O$xuGX~K_ z?@w1?P!ofE-sahad=`q|Ts~#uw=SQ}_!mOKWI@B&uaTL1Gk=vt(rIp9u$*x zYQ0<@OMWh&w`gIrEBi2ajKGDL)MCod)49#JSd->+;ob!#j$-Vy5oPI<*iy+`^zjTH zVHxv)Jcg@pA2sH2wMm=eDd>=$@K13!KoYuj^ysC$M;~t$lQ%SD<32=0@}%chRlUIX zO=FrgYY8ph$(U232w#>Rduev9n!+t+@L8}djG1V@atO-S<}1HQ#+qY)^sD;%4*GY# zY|Jivn(EHbpN8s_gaZBbWvWjnF&5GNAMu;lS1$kDSspy}AA){0Sf762`ZR4$uvf(E zGjzT!j8~NWeUMj_=QVgmA@Rbwe)c z0mq!aZvw7(Bws9IZ{QwS~}PmRyCBaFFJayRNGyUaU+ zS5ZD{m+I$1{xlu1gg^H0K!411zHTHc-d|q^f6PB$r_am%kLPtFy4haVTl~fx>0WlH z_^tb^w1x_Q{>LXp%?tr1X`O!lDHO-G|bE>!M-_UHNVNP|3KBwAReDkECN;{!>ZilZK>M#FrK=YS> z{+V!MKGi#bS5>a4r!@iXDb!zH0o{mFKU4*8^K6>^6Tdeur|1)u_?~#7*N-adC*LD1 zv7tSO(!V)2`k4m$?SkoF8$`dcpucK%4D?rE&=U*)Je$V9_-)`{yzuic<}>p_pLB7a zuAh_jUy*dyL|-vd^y%w6yUq3W`yWm9^#xt`V6%<7=bz925C7Vrul@9IO!ak~_y+o_ z>3B2!8`2Pij!)KgybJ$Eau4EJweP+{y-|zI!zKXZchgCN`ln|Pa7A}t%0Km4J$>2X zzi>@Mo5|9BS~FQ8zD6_o4%DLi2d`kH6N7$~@@zssW{ck&ms8hF?L!qWbp5zg=%;O# z(2v-(BGBIEh4w$IHL0W1PPI^N#-|@4mAnrAXJ{pFr|yU5d zDYt*(gKWg5%NMHGTo`I#?HV~H4E*sSf4gTB7WH;tylvkG3cUhth*v~Kr2szWSj z`xdRog)CZY+g+x^%hB@EqIFJNv1sjmPJiuNv>t>Z5?}7FX1C%aTDo66Vlq>@L7)o_c}!r#2<*GnyQ1ib;R41Y?}_gH?$D=rqkaD59Ft|w{> z*QtEr8vA9;)~&~cu3KY~5yd9ibB;PMpoQy(V&NLEwUz5?9^+apTUU@!{ma&?@JPV^ zj_TKztS^T8&5^NQJsCfI{w}TZXTo=(wd$^TFVK4|ufs;^Yt^_{sq_1rjrGXasgtf4Rr-8Y!6cSb_{PNu=>`eH?e0*@IIz> z>DRC>{jz3~cz=cXNxt1GVw&*b!hH3fe24m=jJV;>p4s0Z^1!cKiJ@J2$}Ysq7K_Qa z4$7|PThev&zDX>?A&OH4E(QD6i5hq`scIeO?YM{P+c#!7FM$0gUcYtJ&I=mw%s9-$ z83dg6b8B~X1rNFM>tV=+dYMCt$PZuevvO^#AsF9vJM*C+w7Daf_WDum9@gkM#$#wf zxk=8ERGTs?Z}8WAD1`B-Xc|cCxl~-?Wx0te79XeavC;tAl!ko|v>PFv9CS8I1vyb3 zotZm+OcdopQG7qhQ&h2ZX6~QAouzAcuEMa;`ysy6;$lyw6;0Ynkw@x6SMWHXXCz2k z*njv|W6gBuH8Xfi^qT2}xfcvI=2Fd2S*V@jFV)^Qc+L=6$s3@LAq&PE*K#HG4J2=d zvqH7&A4qonmI|VQB$)s!M6Hiki|<48r|L57S;3s`bd=3od9F6zMC5hLkzFK3e$bka zIE5717hF2k>?f}Ne1BJ;<7aED=IZUw)BC9Rb_j&M_IS{EGt@tQ1*L`UA36ri-gybeMCWk-S5(s!Y#!<+^UmQ6IU-QE@k!3fw4-S$3<`ohq+0*5rKP`rC?TPEENk$J ziz#CN&^FAzCdtJkK?Uw@+DT%I*hf&0kfHYy8SEuAX0&rIbM_xRppPh7OL9|-u@lo* zxAm(~wn=P}UpE)Jt@U*UfUcd}U90UWcY{mPk6PAcrs(fzJC~>pkL42>hJgO=i*-iT zB-C=~($q1>R4(SS`{IUo~ z3VM|66Z{%0?Kh?te|Z!+Uo;St*Qz!F_Bb%_^C>V2Rw5N6Y z3xhCf)OEd2yD}=Uy-Qn2m&laxW=8GCe7OO^mQh&&%luLI9XfC z{WE`mk^Z~`&(xBN_||s#Q+%^1Izl?S@{Su{!lD=9!$+wJ0x0cMZMu#0x`ySMrtLF@=}Z-Pu4m$7TGh-y?)4x>mlH=q0?_Ymlfw$~qHA8z9WW3cvo&p&%& zy^m+feGN4NPv-@dLL614{v?SC(suceHth26ZTP&38n>tOA@sxqZ6QAQ?!-HdKh2)* z;&1Y`T3+J2wgFxaHU)Esr;u$S>!H>2bG%fsAK88gv&I9c+{gUoVoo;==-9%C4UGdW zgtreKFZ2(-nV))BS^aNFJPF~A$R#+K@D8%@9qyV3tapmFg<1Nl|J8KYqt=0oO?pyE ziN6CEo_qzJXBbA??e?-!F3~w&O8_c+p?UUg9lph-CZ5iNDoF0|&3sPK0Hm-tr$dxb4)iperI~99 zg0h8z)S;&-mtCi7rIM5M(lekwf{>L_5HgZH=oV2koUGjii4FJs3#h@)O$j!A2g37Ic~Z15L8|D)_|t+LDe3My8M zhocM^V25_(5Z`Qm z`!pNIE+&a--TtIogZowtuMB7LqxX?eQ{GQ$s^r;cX3uC$z2gS%J28$OXxIETcAJJyJ9?stHf$!$=Hj zW;c`jqBpZGTxEiqSu*clLz@}tudkUQ_$(?FlA)!>BGJqSpwriyBsDXI6R&0k1WvS6 zUo*q~FBpvZo7pey?Nwy953&!Tcw%VX>L=dJ=0RzU<4@WaoJ+0uXHq*FS}#WqJH~m7 z!Ap{NtUtf6-EkSSK~8FSS5-GAH4PtpvCVp8yR*#K+nqo7A{~lwUXJ$r0m`B1iw<>x zvE5Zd@raMLQ+RvZfm*SkF92Y_FFRZ@$<_R^FbPKK@-QMw3{Q$pq7~BG$_3dSKBcoOhGX zQwOWJH^{PfD``)b*1vRtW_^y>8SKP#A?aVx*6A=p(6z==$pNAoz{Y-yq6YkZ-++HXmJ*b@xVi;uBWRyOf7RO`Gn=+mvC-`{{e462{~-~4~6pG)cF<)Ftz{q+68 zq<$KcPDH7nXZ8Bw^Ku`X_*GZa?TuKP-&k=SYc8mI$t%Nqr=-vrunOB zFi-SX(|2Scebtm!2B~*O4$OasEt+PSeMMhN@%~1jXm1MM>hWeM8AcowSoR|*n~c^# zWboJH_oyKD+=E&@^6{QxsJ~l7>kn6#Z$ejg6dVUMD25lbeT0H5;%uepFEWyxZDEK8 zU`ABCaTnppY-05olPT2L(yqaW?8+5j2AcXWxDPZnNTunsKgEqvTSPFs3_)8CKA>wI z;r%msR$(v{8P7Az1)fIH@eEEJPp7DO>S=f$`97+AD(_ENKCkoHdVWK)Nqs%^g^yRCqT<=VOyC(B9nV|i5|)pf&zVP(&+QtX_1{L7&qayj`He3ZMZ&Z4b0MF< zMaMJqzJ%p7Ix3#)G(7Kq6IDJn_a=md_`fqS?d8mk9ZshQAfXKKD)>k1Z;mV~Yizd!ys|Y;3~vc^1Q)$o<9NH9UKE zMU~IhiQ`F)if8L*LOy+><9XqpgyrMh5KTTkG(1apMwL&4#PR%yiRsAo^)a>#lfQ9I zbUc&BBrKl+QSn@<;rZ8&sPZ{@cfxp9t&b+3XFe73X%rpL;KcECii)S6hUby(QRP!P zI$`;|&ewb*^%oCOtOn@Mp|7LjxiN7(Eu!Mt|386eXmmVpjY?QP?zPeM=XMRx`fX9= zb5Y`We#3Npow8j^V&y3K9@wt zb5G)U@}uIpP{T88OH}#nEJ|2DpRA51pT|EG@;SXZDxThnbL<0w=icaeJ{y^^ ze4bquO+J6u@a*|2s(h|a98YRgJX>*P8`GV*4bQ(eMwQROVF}||#n+u8_3zIt6!K{l9navz z@pOucr=EuAkquGhQ+a2?@_D^1n*KcWo{-O>FQekQF>yRCqT<>AuD~-iI-a+NCM+K} zUto%)Keuam)~}B$pNkU5^BZ5+iG*k6J3>Bxi;idJ9SO^4bW}XoX?WgU7gatrLlVaG z^%v3f`L(x&d@hNO=bpszJYW%<0}oT7%w4(mMpOj_Pc{(yjIt4(N@47uq!xzYMJ^L1W79t+AKcyDbNb6 zn8~9oDfffY1XtDGtBq8lm!U6&IeXy6&(P>#}uXAti6jrkra0-!@ zS+9T}j=ywfz>UStD(jdzVt)+HKM_Cv^?_#EYL31$w1Slnf>eC_Z6ighY(W%c623zeZo;*{2DIwj2qu6k-`p++@$P+^kuHZB*E=MWx|6H6x~9xO7CM#jg`?~-_sF%zOs!knroP^UuW z@zQTb>1^*CLT(4)e`<1btP$Fi&ecIbYcZX`go|)Vd)+b!4{E3hqV%C=nU#uD+uO^4 zhVJB}#V^{ilJhoS`YK^qBBD>QJ#-khhwg@VC-Tw$1vN>}M;i>a{Z0SC5){iAn2P&g zaXva=zdm2l4%)9TbT??fz6bmD9oSp$P%{c>V_gof!O7TBH|B7AN?z_)Z-9ZI?PODK8pdbdx%eJkEV#hV^o7J0>clyA|OS~wvh9SGf# z(3Sz@=pwP=J#3}PiudDUa}bdh*rCmQU72q((l;f{fO+SYN1GE@z%S93y!-Q3-?TFz z`!-B;^T7+)J1~i)1YRpZk+rp>{F<+fsb#5`0szPUcMSR^!4%r ziWUrD(-s)y@EQCeAqR0;mH^h)01QVb!tA4nP1$`3*7LeLWR zCY5{+lHQk)1N%R4uejI%@&Y}F#XZ4LR{!r&)|%n?emLH;cKvkI!dpqmeuFR3%7fd_ zz&twFk|fTUX*;-aZQQ&iLG34#_W%uu4Vb4~Sp=oZMXb|(C+XfZafr)s0Uq^khoE)X zL!VsSj=BLVLiZ}9l8;CYeH)XR!G6F=VB^6HM1Lz(g*NQGdh!q0WWJReCasd)b~{fV z4jV#`k0V(`BIkj9X*X+@*sy&PjtWT{frHzjfB70!$q$I3)u#zxf{x{&D^4dvkkJ~L z*l#b1_WlQ4QMA82L=L}e`Po`;7ibl$EYO8a^3^PMhQS>~S%{}f?c0C8$uyqE+dP|g z{#E=I@lV9>ka!wvG4qR^YR331ouNR8&(gsUOsiwj|J#)v_O>SwSe*Kq0_8VE82=-M zj?2>B6UrIfa$)zhNR@NePwME%IpS<7tIcVnmZ#fF+WOt4FWnMkwI{F2S4LS4)(!io z>As64)GpX{B3IRNgLWCu3pzYw#*1B=%5idP#mPZ~%-q_sLGqlnZIZrV^u<|X7npFm zwWy?0$%Ukk;z*kT)pVfYegJw;9*{p|qbJ!K_zAUh{(}M_$=4iWhj0zM393Sy!aP)j z27{_?PhM@Q5yb2cdlA?Rz#ie zM79)r2)`zs^i0X+?mgXJ3U!5l2aRN8vxEg8Kf<>^Qt4#lUKs5JBv(cGm2Yc9`*`>{ z4GZfBhp*~YmU*kLp775RuwHWjp{K_o_IbPs8aC>c;swdqa_}g%jD@!8;GR6N@f6z~ z|Ky{2=$9LypG)GG8{mRayV}4(t&Mx&X-I%fwL1=4APtvIjYpzhi#@n6h<)ASX41kw zkO6E0(KpzHUZN)@mjRp8N2#p06Jr zov)5225<+4z5GW@A5W7EK&K7IxC(o1aceIL+jm~c?pl@M$Qe!daJ-7w!~|Sicr9dk zh!qMnFRLxERkV1Uw;d`&+b8ZJ^MTsOKb0ggE4vH!3Fm`B7rj@f?Iaz0`8hN@9H@a~ zTmFN%MS;#;;m!d45(TKlIXVYehWB)dg68%XA=HC*hXSP}Iq#EIg!zUk#=z)x+b+B! z<0Z}vBKH450+Gquy-2;xGG1DelV72*7FnS-d&q(FV1t}LnXXXay5+QG`U+!*=7~Yi zF*vQ7w>@~t+1g%i92{u}Ri&-1R^jckzQ{vbjf=fxPm9fL8V)LX8zGb4pHfR3UjWVV zq+UQ`u;;8PzPRdhJzdxlJ%3ha^vtGC= zgFQ~Y7vFCxxm!V%Ft~W?TZmB26!*MKZaRvK^N2I(Eo@0fp4tm1?2D~-^=1(rUL3L) z{MB12ay|K65CylKkHJJNU8fUy<3NV4#c(HiYTg&t5u33IO&E?RPEMf*&vYOfuaq z+`-qr=)8UwH)~>i0;HUG?@;~{1Zvn=K%n!2`=B^H5uNsr|1q3rJP7qYGKac-Xgu}V znFYYcatS)LF_WKXq#gL$;f4(TJmWBWX*}T28sDG}&h8=Q##Q$X*d=)5{U=Csx$}MI z>_jip>-HaDi?c&nkWPhEf+7*%0s_EM|2FO~FTUTfzkER^KG2SJTmdy0k7DfD7_GRh zRFcYz@V-jTWDaVBjYThxG1_*N`aYsfphf!km!Ejb$o6Dn+m7r$wkP!pVi|WB8`*7- zyg={^R1TEvN_}=M-tg_^bb2EL#}vK5q5ZD$O5aZGZRfCflS!=C_eLGu@U=Lr#3^e_x}-*j0Wks+%c9O^Wz2(6iV))RM)Q6$#M>2PUG@u7{W|hVY13+12KW6j=;dE zMY?)R6FFz4H1i;JH01|y-TCFR>HvH+Gkz$t5M2kZLh1xjP)o#KVcx6Y4h{P4`e7gL zP(hTAa~5EuEExsLPjs1gb00oA2g5bVJrkS*<-jDVS#tmb>}uyeyeLS@oBk-lcGwDw zzoG{DSs|1F4s=G6^yCw~{~Lg7t2*-Xy7oXy5#;F=XWJ~)BTdW z7d>>XSG-xyK^Iv(hu>4eVU#?KhEKd@su)Ocp`<_<$}M=vw~aN}m1~KzYt>}bg~7Dw z{!tA}Gr-);TNc606!tTxht=6f&gqfn9EN{^?{UKHB7BD=_Yr!?saL#<CGh;vXU3l^6%{{5WMVI=`Uzb145AfBtkWe>vsH zW<*~(zV}6xpQb%{IZ%akx$PFY{8S2#N!63wuX8?5rATh>w7O0uOC`9qq!!|qN}AFS z0SDLc)Oy{#81mbL%5frO?Wq(@gYxeO00qP$mE6jIaCWXFcyyb9CQp#&F{QX5@PLUk zOu&h zr?uhBz{iw;+~__`$;26JP7G?)FH9p+!N;9!1M@&3XaQ3;IvX{sW6Cl6JMG_z+r;k} z?$+fc*V0yzis5q#eWk;|#k+-jhE@B*qed||P4h^<_og-e$oP_x-Wyia0Il-&gr%=; zgsDw~4)?VLBKe&O_60!JdwX6EyR39ma2N!Y%mp2TLcI1gl68u&u~pOxG?X|>_GsPG z>hWcM>Fc?JZ*wvG`Wt^~4|zPW|Ag_q(Mk|TDP_`I$GHdh=w6KlE*kui7&gKh#w*Au znuqcAcK$Q~iT%!eIzad8{AzD!EOfn{pVyanfzGiJXGMVKb~})xQIj#gf`lw*0T4Y zUZ}8h)J3cwIwlwO;8{H2-uu3gPONJ_&axjU$EQPrbAki$Z*K5Puv4C|^t0dy%I`)p zd%3rchc0}zjnHWyc&8>@Xo%^@QA5<{Q6Q&Dr`DAvg8O@4YCa3(h&G`=m*6jx}^DD+-HURnWg6jeQ_CQ zM)NDG<_yD9xBL}!R;-9!g4RGzq6mod$u{rT$#E$-TVCr228whWvl@R$jV9F~S_H}q z(=gZy^EgEN2Xd5NEJ|%J=T8tGYf;YtoTvUkr^;}jd1w&ziO}-Y|8wWS2~wBO#Z&m7 z`5(x~Dop0f#|C}jDfta}A&V}ohEH8ks{u3BjjR*G5-u;q|8tO8=85T98Nq9PrEBM+ zL`J|{v6164gs7r=E=4U0dh4eF1oFeq+vG>+8JV5GieDk=4fv~lF#iP^4J74=?#Rhl zaW_8=`p<2TmEpX0V)Q^sNEXwPpa@QVdrmb>=_>r5YW%MDMbK+)U`IT^G8zc_b(CH( zK+)3CQLerNU#PcEx2>``e&AY|CAgjwqX6wJTR7O(!d-OPsw+jMHg?IjjJNht;Bq}9 z&(&%ge$Yd>PZ7L{DB#HItz=kS)+3i)YgL{vG645(ZmjVDyyiBTCGDEaIM2Ry zVjl0!#LZ3T`U{2I&d$pgY2jx3|8~=rk;V)Lm&GaeOy6yxBA#KR%xU-_n)+9m#PQ9`WZ9gonRZR z7(wcA1q_BebC6Z7bcwf~c9=hWcAmfQ*&x{?Ms@zsP?9VVz7f>D0y}#nG6JO|a>~6^ z76(e3YYybah~kr70*Yrgdq+;5tLf0rpaU=4V^`Jl1Gp3d0!~si*LYAz&9E54ZK?3G zNrk&2pu(T~QeLfk1M%v`rGGWApx)pNNOE>Y4fmUHt$F{;`(I&PMr-Tq0wHl#rtWJ` z7pN~X6hgboTi*mmR9|w23wK)iEdQ8(Y)om!A#S?m6VM7nUY@7!0hzpYTdB>jg*Jzr zy<5QMLo3xM=vTBFb_k|A*jw-RG~WM@@98%&-V>fS_H%sySL;2|#%n1S>uK~(Ksi=S z+Be}3{Q~E``1fkJ!DhCcdjoNEqZXq`@vMJn#<^tE&#e<~XOfPap$@3=28PEB#KaZ| z|J4V^^@o;L)b9>n%P)SIW%NM7RSG=)wZCQtFOaH9P=%=As?-4K-Os4RkGts}R00cn4u2QIq7VY_4h1u@bJD z3)SaPVledJ9of29)8!m$m@u~F27oMZwW1uoi&A(!859#83{ki}0ubABsRqY!>19A# zi^hHJd`L5W-`x~Cyp5cC5AXQ&W$tgjVzMuMZb^WG9rmNaO?!Q%E4=l4&|`cUh7au= zAHLKd;6JQt@A$BoA8H%2u&SQm<6W$>?h2m^v9{_BKIAXm=dF7IpcYxYb?fnn9tmx& zPVEMPb=R*a(z@#iZxrhcJN#6@YLg7fW+~jF2Eb}035MCGtJ7GxcG0L9!!9&s{Xtln z&==~A)4=9THlYyh5){R=8IO@@^QpW@o zL04s1YWKGb2H>}7AM7D}QPVgu9F*|j!$GqPF&3LK7ORmw3?kS(G2!PI@fn2280{M)~&{C#c8-x068;lJTA{Lmh4%ff5wP)oRCmBN`v z*GrDtEz5@*j22b{3)CL#v8Cu^73C9bq-S6t<8J^$o5!>^1!}$pC&e19M;3d{D0Ur+ z(d(QOfTsm^3GyZ3O(oV?kZAbfX`;aT&C0I~VT%PGAk3x+v+0j1urRYfJ-ukn?%|Z_ z$7i$0;NVoVi-4S=cFo*g92Xf69iL!q6OMM!VHDIeOy473vABb9@Z?C8D&Rbtget)CtBu0Yaf>n}Lt{-mrwuASLkL5Da z@W7mMR-LCVC4+(r&uVh=WucnRhcH$FE$fL*!A|d`Yk%-|MhuyO7rZXqPAD_+`WdoC zydXO{;^Aqgu)&p5zZpM>OcE4uB1}-|Zb#u*JfQveXaZ-T{bk`WNQY58g|u z)4D+&scc>7>q#1_6i`3VU*0hW+o*o%gE^?dNW@W|p*|^;mds66JpG|BmHLzFog)b3fK` zh+V@*+4G#NV3NZJsc4AE`kN7eqU@M1{aRJil z)EpMb+;~~2!e>}x7>x-Spp2sRS|5vPw@1~Navd~q14d3V&(?esuJB!kqK=aEOGU<3 z$y&x28DFlx1nB_Se4nM_VwrHULI_}E&quKaYo7{9{!oM&DqrnldyQMN!gr1fSKQ%S zTHipV@LvEiYmGl;!O_|mUe>YY=tA%?U*k7f^m4Sn6Wf*FDIZ(CbDS^pCr0=qFVq#| z)D}_erq#(yKI)`CU*t|Kq2^4$gLtphpLOBp7c|PGr!TaMjvt9%g85R3Ef8j%5P$^V zyV$?WD%ddYAdg%XdEYoiuPv}%I45_^5qhm~abEd7xa*el!$#ivig{F>N9K;b@@3c90zLb;=8EC5dkVf!w!Mf-#05h69$hn%@E~@5)EZxH+voxM zm0n+ta)vhbdrs8a_K{B|6v!G#jB5-%kBOdnWu9#yy>(AuJY+EK?L6XYXW&@f&Piwo zzyQ_w+@#xa-^TptsbmI&Wymgmwt(fo=9d=m%hf&8;}>|xt_1#;egVI{t}aI5-@`9W z#EX#tnqM-K@XL<{#4iEAT#OykU6@0yKw?8Nz;E4lwRr%BN|H1#RV|1Cwzx)HGUd23 z`)V=6oLpm#MsA)$1}Mj& zu~n9;euA%OBhUU*JxQV+m^}mxgzsqp>jdDe&Q>E~tQ$O>tFD!YD3^lSqNw2ZLUx{y z?5GwKRU0FJ71Ui?@ewH$L*rKfsk!o!_cHLlBJ1*vqLZCmruP9%PBZI(xzl1)KAQEI z+zYzSlFfSOeGmFNqt7r&=S>kc257z_y`B)%}U(>}$4~Q=p6N zOF_LJ+8qso4ek65_hdO*kDiPXR5zd=_k5JLerzG~F@7%>r4?U4W*O~>KE!$@=6sYi zg7;8Mg!?%(@Hp--!-8g-cWSllzjp|a9az7+=5|bJFEjr@fhWVWvqyV&e6>Gi2G8$5pSHJ* zlRrEJejT_Jwd>M5={$TgoudBQioAA#&|ZJ-QuuZD*!(&N=;{KcKh?Y=sYgxZUbz*)*KOf(o-8n<^a?0VZz-8I9NL>AD~e>7fkz^evFQg2;E_=Zq)DS_As4uINRIim{rfs__sA?Gt+j z=KhlYtvKiu>qn8RA5Ayue!Pc}Y&+b6(vL&JXXV4Ymt(pz*@}zv%VAYAe8l8@nA!|o zBOL9m`qP-F#YzLt1;7mQ?oy&pJT*nD+FUi2v9dA zYGg?75nx&r`!;SHucD3M%Uap>1x=yu&dXyb5F=@+yPB)osye59!MspEkU?Yn28YJ) zR&y{CEd?uB0S4;B=v7O`gh=ev$OtHwBsochKys0qnBv{f^^&h%a1;-nF<+DTagX%3Ciej2Y3;;{MTE zv);VKHs9=w*+bN&u#;>C5g)}O!Y7+WRB2j7_~^2SYEhkO1la8%)@|-{Nh7jzMuX^W z14o*PqxHl%@NXK?C5y4LF&%T&*oGf>4L@*uKI|!C>H=)Ok7Jrt#s=g4DAr|8LTh0= zP~GBvxmu40U+8n=4pL4{oQJeCR+IC^?I5Sk4ExkTqCb%kzp;j!2si|$yXGPB=oqaP z$P#BTEZRA;d;}|Csji(s;*T-RHqKkJT(UNt9u`jG#R8~8?P=lwTuqOh5&`rP$vkbudCCzmeA;;wHJq-_D=F&%r}| z5uFkrWL$d{%+_(oV>qxxW<1CZ|3j3ImEC{bU&)qY?o<)aVF6U2mdO-87+cNE?yv8W zk+kDgR(Aid0S7#6+5OGdujN?>+K!UrAE?o^uYyU;$0#{9qk6$^1}U&_?^O@n^9y!O@Jq*4PRZvu zfZy~+LroF*jYFmR&Gc&{R7y4|nm>d|L4P&!-&euY|7Wt)%)`-Y<_-q54Ks4tS0SLX z^Q~XPKkLk8&-hOi`w@Ta%U$h%jqRTeF>~7clHVCIooqo{^BZ+9xDuNPCy}GtToC~W zA{UbEsZ)-4;e@z!$tW1vY?2zjQsl(PuEFav&4TG(nTQ@*C;hy2ub?7b6vsLs%1}GR zWNC$zC+9)%c|s~@xk#7u$|)$_zMSGbYVI1 z@|_2~)KJXs+!yi*$CWUu$$g*1g?#DYIDMbQd=C+X3LYm;Dnv1jgkfJzmn#tTsp~N@ z8aOi1{WPG*ShB10$!JoAndrV%-4AXug@-oj{1N1`J!&Zg0)ImbRj*0BL(W@wGsZc} z9fSFK_{X|0;^{83=LKhauK};1E+conK2+fCjL2-nj72=B;h-x{3-G)Mo_dy9XA7Kn zMqsaBAbPdFO!7eSg&p@sAn_`ysXz!_4b`!#hJ8Z~gM?&I^ca6(AZzqx=)HA}r8S5D zjq$7)0nYbQV!d_rF|eeecEAG|dQo1Ep-0?aA(*4>;9q+s2LC{ui8lxTPMl?nshb0b zy9U1!t*T3);$wq<3A^kbe4bB>H~7?Q<}C+swPEm~&1|gElMML9e!!OLFF|8t#Oa{Trda5^?S)s zV7p7;AZeZl8OZoS_WcSXh?x?MzjrM5ig9-o15kP{KvV;~9QTvC?B_ko2p{S7-Alv? zdb=V+osL&g}c+2)PjY6Kk{c@F!jy-buIei(8B41244G4Sx(R%Jp3I zO&GqmX9$tE5xtC*#VAtx9zUH6uMHz6g3G zxNTw%B7tKP`)Fw#iIv+X@-R64S&x`0j`Kj6v3P#~20B}=n>ZcNWDy)s*gLjVSOmeBnnP;(IJl!bK_k5yW{^9uO#trbidV*QpfmEZ z`LJHC%K?xc#B3QvG*-L6^R1g*V>o6Oz+aB}Q-3v5<%rFrKv0LxvlcHnLwTNi108?~ zFd3|?Bft=sK7o9UDeFBLFbrQ9ouu zpD(P<2A(_>NA-Fy$y1k41T5pyTmL?%i;0Vfr~A+;k%6s^P%%t^cihGx;SN@a;GrnT zd9#Ynlt`yv7-;}zg12aQ0LnpBHus@m5>(0cnwZSQvcZ`~9ujU<|AHNgzYHZ*zmC@? zNjBATe=vaIewmyt-SO7-ioL5UDEinJnVnDcKm$bW0k}VsB)7K8@m%vHuD5P4#KWv8 z*Qkj5*~Yu70)0+b$XJM%5@)Rs?MfIvk(mv6Uzma4UEZU<=7CtmMso`nYUuL(YPA3& zOvrm}HIL$pzq2StrT~b7cG{`Rr2|LK95HhUTPJ70qGz$Qq(uxUsD9GlIcTwY=RNd- zh#C}@gV|iVn5|$W#@xe^Fendn8L*7i6o!V!E3)-wuKdnXhZ`c3BZKhwyKIAibK77~ zo+(7BTopNth%j6SEGJ&*Y;{R4-to{YPP}5Zr9us@tza0C8Bx8vYEDieryiZPT@wbf+9;{|u7`n8}c|m^Bf(61DAez`_3c zk)~~^iK(J4xzjlPJB{kykS`U8Z1>7Ihd)^J)y)s6}-nprHeyl4w_w z7o;qRJ}Selg%w9>4(B%eOO(1Cco(s8@k?_jf9>KtY&m3CmTn8)jp4`Q6f0e^TCk15 zgV~Vyb);nWT83q-Z^ClH5!=mxI819M5F_)z@eZcfB= z5H2ON8d&AS{ie4Q!n64{y!YO)fZx{Q`4&8*>52>scq4$H)KKV^XA{a1Ajbkw0BFWr zRKu=JHURQ0fE@rhK?4vKuK`dGHG)Xt~ElKqy%8xF~w?<4UlL`I9h z{Dh<_yEshZHK67V#^?yLq*7NP0Gx~Q2Q4mSV<3~KLgN*q9Z2IDt$`L1h?FSTKv?Ur z`vACE11$E3%FFRs2<(7#xj{di)8bR1(S=)NCh2vEbb+Ce2qwn(LvrSq$oNx+BE|2A zqzkj3HoI^F8)1X|2lV!oVy}zj8a7uxC50uk`nJHjJdb`Z zBv)kuaQqs~^rb6#9KEO}3x^yKVOd@P47vjYqs@Gj(rqGo(a&QIXfqJ$DA&w09ysCo z_jMqS`}{lZbuRthIR4H{=D`}rJw=Hu$L7czw7f9C2+Q<3gzq7A2E9)d1>Z5>A3{V5 zLef18fg{L1>=N^|(~*S0$m~Rn7UpeCJM&NzU_26k%E&qd2xD$&uUfIZ<7^@6(y;%8 z$SpX?plWP}$7#^oFg3?HmgD6>XIYfjqw&0a2B!2_yjfZFX&MYP7y7}LVJvz!R}t?L zI^qdq-Ya?!h{-Yvm@m3Ot0;X1`cSgc;bH?}7L&qqT7DhvAHAH&v6d6)I{Fp64p6rm zJ(v_Jy9#K?1!a7eilTjig2^9|vI{IcA2lI}=d&`SdfLO&2MEqE$0H_2QH8}i(b{1dSa8tiv8inxOhbGbTc>M8) zK+4^C9eoIM_+(1LTs#q%Q!)3+BnE^AK4#0;K#G+)5E3?=jB-LTLFgV@CvFb>N=%j* z2dWzcp$P95v4Dqi!{YBH*W%Ax%jiZd;E@n8 zeu?RAN=!wgDDfEEj?;#B((SCvojPK_F7*TcsDAB0wzx%k7jK)QWgL~=(LSV1+)`hQ z3U%eGn5j$6NLY%_kv47piP~d#v^)MJ@b8*uwwU4~((BWIIS!;_j)<9~lnj`ojR4W+ z=rx)670(>)^KUKn1!^!=e5BKcdnuA-L%|~291Y;;84ZI*u?`HQDk~qeVh2u&zlgkf zP69lwQ0z0{JOP8eLOA!+st!i2;=xouigNAL3AP=Ugnh=-8ljE&GIm+4`f0lz6%u z)n8B~rY_|ivs+yr=~kCzEE6f~gC@1Xdn$efLO_uALvN`OzoN`Zq1SDoqB=n1Mj6vw z5b*>bwDdXf1nF<|+YuCf-hgr@f@9BiE3lz<1*V<{EJjQtHUD87QvZ@#h!mV5rY36s z(44Og%><7ur7hw{|IL0nuePT_YYI=abj?=AlLY z=e4q_$q9sOaEkUc#M=juw}IJWumx7vj>Ayjpm{L#i$Ao>=dD`|OAlT`Burbhc zCyxl$KSEvs8`RIikD~QOaLIJ2?70mhcX&4CV<}vFho2Y8I^L32Z%?@2>OUTTJPt05 zKMNV*YEF=&`@YZ>Io{4g$hP*Av6!EBs{*J=f@24rj{iM@p1akepWA0NFQ$JHf{lHn zR&^nWiTZ-Gn%C0K_#b9X{x=@-lUmpx4B)EN9ducSbRjF_A@H z%wxm|^l*8Jw!WUv2Jcaz9s)k*!6aYbE+>w}5~FS}YQ@9Ne5n5$F*i<2mFq3nP)nSn zQn{-4d!kO%SXQnbE9L4oJRnvUTU*)0Di24M8h-G3Pi)XKh3xof-v~+;>DH0*;)^oR z7(@EX)kI-*#u$f@3JBbR6<<$meG4ZAz68zY>r?4!L-V66Y|At|3c|%mOcdrx|m!P`^7Gr{%6Da9E&u`v2m3S zEdX2TP=#5-GCa@l6Gs&5UbqcyLsg*8#+b`tFnlf~2UdZ=(5N_>&>p!p$(ZFRVew<3 zI+TM;e)K!}@yWlEAH~k@+og#Q^@T9HH9z7=avVM^p~tD3o+q>?di&8Oy^#)HL+k}9$UY6w_=T*0I!Mca4!yeD8rCaNfqi;QoF=0_)@AGDjHL_8a(?QeZ6 zcsddZo|QA=CjU(%L+2j4AfT?e`mmZ;zoLwtJRJB z$ltE!4)fk!398OJoRvh1uYx_TwvanQOQXXm+~|)P7m{@rJ}$*a;!{6b27{6-t4ahL z;AFbWr85#a!RoJ*!V^%fAO}e^zD@UESP|MeNHe$-xKk(p`uUT5Y zT5a;R7g7ZliB3TO#h#*E4SQ;T>yoo_^L%rIH&CvSzbJ%UX}m2f_xJx{un(BG=!^ti$LsDClw+G$PmKyA8Qk_-xvbP&!cVhFt3iF6wPM>DtJ3 z6Ya}{a{UY@*VJ--*$;$DN3Q<{$mng9>zbHcb3RKp-`Yr6Kkg)9v_d z@;i_r<%aM=6reDy$+3<0%T_&M-;o=%Q3Nt#1YsAsthLMr&N?~jKlUj7%gxGBfK#qx z+RatNf6?^Mi)$erBL+bty=q!W>%V6m=uyl<;vq?7Ctavo@hwIQ`IU;^9|)(#vrk@= zhT7^+X9GfSQtS8HyL#tQ%_eE6|3X4SeGnfi?~Kc+{|tL~JRc+xetPz9zjvwkTGZXF z+_ZP^#X}N%cl}QChapb~nB$4UuF))44~V=ZvUe}q%W+CInw@s&(M(|Po`lcQu^1wY z=&8mtvAw&w#k6;0XU-*-fjh1?VX4#ms&wvSPlExR=saD+P-WtDL1>4$s~sFCktR$| z9iBiV9SOlszLXsyt(@E-LgEo&@welnx7gll% zC}>fB$OZmlFWakL8E(Wl`Vb4q{a;AIp<+VUbcboduDIhH51EUX8Oe(08;wqGSj(sz$ zLLr5FG$jCHum}M+iP0w3h)nXnnVZ;DfZ6K0X%wz2;-*7raU!ErPUodGs_ ze9VMl7`EU{S)`yL;#!UZ7||t~csF9Y14M)x9b^R|A!ve3F6t-EZU)WF$_ntqm4n_} zcfJOY;Wok^TJ?a(2D%ypU0$jO+FREZB`A)g(I9LKwG66?P076|u=qy<8TfD!0m4C! z9J)kJdKzO4$;_<`Z33fltR3UrdXZ{>mO0ArfD4WAi8=VGu5Gr>N|e872fQ{knmq3T zr`eysaXmA?iv2t8_HsNkACdk0zg^FdI-mOszgdUBaX=P&5N>N5vx&dj9-|M0-ej9{s5zdD>`VmnAUVqErA7X1oBRjgH&2&Xj6|vLiE#xF z%@Q6$Q^HxgG0jB5hpbxSX(DgkEno$0%*;YFK^umd}(qxxdgHw z!4?`>OFNEasV?}zs^WQ-ar|Z`yDcYWjdDYba?DX|?-vXz&OWAayot~B=jR3@W6G(X z;11jmkDyxp1U@jF4*CBPzlJsY~%oy`(7r zcDu6xc$ztpWIh>8{e{qW_*-hXx$+@tcVFlma*8;=^Vm(XK*iaI-N(5fXmJjuJ0z#4zMXomVF?P!0_*k;lfH|bVH!6(E@s~_dsP4l9*L>dI zp7$psz_#`k;9@!Z3TYN$;Q;lnrs$)sTrK2kuM(r~h&C&8*+PY?ekWSNp=Hbo--*8< z&=cWs--zeW55uF4CB!^6U;;_)^bLSj`)>#ZxwCXEPAnK{k!<<)fAC)J$pPvyq18LO!3VFfS5Ok^7uG792XbCuqoP&M7QgyezKXnxEwPWQi(3ilyty|M9jr8bKbOu zUoxo3Op4q{5g&rm86sHvSq-)@gtd0Em?~b5vstBI)vTaAYyPr)%YcisDmxCQu;wr@ zO|F~Aw#p=p^K=gPavLUldW&YVf*6x^0+SV5CYO2}5GJkpm-3yAe-hiHQ>g*R0lb#pKaL1 z^211r z?3c>|8md{`7^TZN_FHS-)Ap^g)cVL!F9apa#@Lp%sC*djR7QDmm(E{*%de>xThlh{ z(JzUOz89m9rL&cJRlN#jTs?B?UciywCCXg(!(7whC7am&Q_ZtA|gPB?0hA&!k40=Ux<=2@x-S$m77gm zz(LU2lo=Gi>VJ@ z{^beFKWmkz{tZN~%Qo(p3$5WGR!IztLl^rnlBLlxm-|9uxFJgLM_ksbmA^6CI}mE{7k%$#ca6@*3YCzZ01gcx)oMO zCC>|BK9=_jFsooETigEXV)+c8i?(+$VxSNd3hZO8`1=4L5XPk|m`cs^7=NRJTy>ay zm-M0wE9;STLyXX#&Aza41T}Ke&xo7K8q1|(UU+cVuD*9IPAb~(i*#J*EBXjy_95au zeMQTCjfxzD7g+b2{3B0rz`5Fuk?tnvN^T${UL`UBS-SRi@J?Ds z4v!2#GQYiE@7?*_A$iNK+`I2}oBri%Tz)88 zAJ{b)$&9_eKjWq-9&c@I?BaX(_@eh8y?oj9h4>!qn&Zn{$LP_by}rg}NBc6rw{XIl~R`UmwjerCb&{^MG z76lu>>&?)+QNH62ReOmAItE;XO`E@* z&G4cg*VSySYB@~+uZ83rxSk9`GjR%63am647)V9AdD#$_FR z?GNo5$ZSMCU32}Jp8x{!)Rmd*{EhE-0B3f`3slw4*SO#)j$8@KcdSHkPUfmW=wm-( z0~?ojhpt`{$Xv^JnalhZ0sI)h05>xXCL#$xW2X@QZuN^m(JG9Mdh&Bqb80I|^e6HG zAU85JX6?2T1aOWU!^OvN=bEda2r>DF-eX!W=+t3^o6EQyoKFF7ti$Cv?d@XlZJzI# zg}(MzWcZ3c0a~9k*;UaxKMWpF#?&Zw{wEWMKt-#iZd41uuL&G%O43BG)x>S=%}2rkN!mlV9MW{HTFhmw2n|{Ih-3f(c5PJbfnk~&y93MH0HkOQDDm$KK6Niyx*MHRox~|>c&it-S^Qpvfxag%;rUF0+~@r z9GoJUboSe3cS{e%%p(}XP}|TCRL4xp!D$K;2j};t{-R}6=c32DR>t4)<$R&VQ{FtjVm+I-dYiD2MQcTzHVY(jNbw?m`B`U(yp7{|#0-?2j(3~G- zzFvyD4l?KK0zF>?PS4l-7}<*{yV5&znaxz;%)J&UW4?wZWX{*X1;*J=4)Q9Qo9Y~(rd-_oT`s@EL-6nHD{i?AdxWLW)dnk=@=6=-McdV_}gPD zb4))eW*(WQ;dX4oegX+g=czXTYc9l8lS3uIl;i_x72x~}Y!2#Yy-nfie1`Djkwhx~ zJR|nxu_zTi8?vj^su&I2{l6}){uAo|MgOtOkxu{DI{V*O_n$~5>pw~v{a+cU|FTz+ zhrwNbwPPBBZWK#RA1bT|1AtBb3z+Z#cxB<@HIFjt%Y0$M-_?V6T79&ShPa%Y-03Sl zeazXeGmPtbhS9vjmuDE+ahQnKx^F6*sx@*{Sn>!h*!%?a61+_+Z;hq{(_P_A#WUFW zf*U_}M(m{{_yX%-PHL5?PW_4rnz2yuK&Q$b@fATMy%r&aRUBimR5s#O)WgQw8@VH% z{t!SEKJaYn+!37rP2Vob-Tm zmx9}g<#Tzeo(}lYvaj_uFFUJd#sQp|&eum5%Gq%r&uge5AeE8$WgLCD*v>89c$}Q6 zQ`f@BW_-JxrU95hXRHdXMS0`KZS3BNGoe7u>jnbUN~WM*sO75?UCVK?S`O1|TQzwd zx0+Z|)uRx1-XdT^{N*wGon^m(*>yiL>{$}E;jMp(7P`*h6mK6L-RpT3cE0p!nk$6{ zW!>>ch|)iNHcpt|id-448>YFkysKv%zVy~=yJi`VNyN_c3dewMp!<&C!MtVqKw(iD z#dJ8~THF@zVR)d;Rj!W5RL7l5o|Iv4`3zHNHEW<0@GjfNeI8%q{`PWn(mO31>ZZ3kyQY@Fc! z;cRsR)V$zGqhF3(!?$g=yEEQ2Fm_8bnf$_@xp@FR<}It>|0 z`iB~d8Y&P3ww;Q!DH1#$S7DamGy3hGI36HB)h;SP z1f;%id@tsEROVpR`2>x0n{|br zc|+~hJ9raiW+Q8VOivY`sxiwfMoB(Zya+y=ZVrdggL8&Ir1K3)iqYC_!yIGd<0dbp z8dj;?!&&VM<4E~9?(kvx249mZ{uqa!aKjBEn?kGQ@WlI|Qf2t;LKyT4>Dv9J<`4-A=l$a5N$7jBchZque7f=^=qw3+ix69cum12j z#5Ms1pa@*y=a!vb-zZ%eKt2~$*_cO=6(Tsr$uTm^#cC1sIhB0VzwYN~KCs%y&~}upf(+ zPN9_^VFF*;6fI6Wisjs{QjdUALJZR3fSDo-CzP?(x&?5eC)mN7O zZUH|y6cv^dHvPR8pVQIbB}iUo=VddRz*BCJ$}KP z`1E*v!SAMrw)vgs;Rm`|Wt2%8n32#>Jbw)keIEW|v>o?6{GUd#_f+r$Wh&})qYo?_ z>2>xc6iJ>}4?v`74Z6<5n+y6fD#M89Xuxw>02ctZ9reL8=upW-*JfPH{^GNr2MqmUk4mylV6#pE(LU&^4pJ~k!}-) zTJl?lH}U27hj%S<{8oL|ALrxu=pZ{R+eIEk#f@^%Mve%0rd*BXFolS>D`6(YSgRSL zc6&7rp-?h!)*i4J_{kJPuJqB37xA4Ql?dl~tZ^U+ccG+~zpL>}7rqTKuw=#y$!!7# zOGpj2AWyd-3n6xd90s>i43hJOt~1AiJj{T^p)(4g054G)@*N52_DSRkDvB?K>Dk5xL4R{Ci$2A<^F=`8!%xw?CG;L8Q z@^!@HK})}F6>*&Bk+_K-%&P$%~n6^$c3Fi`I}4rN8YbqWGNeuJ=?*?W0GoO z_aZbWRwX2_#AV|bI|Oe%K_lHd4z_sgbG(UOB(f%MpfA*;Zv#e)T5{fc6_WOf!$6_D zne)RgV@pUz2PQ>Z{)%7?FIzZTlY~7zkY!3U*#OL3T0vjC)lksYNZ=Dw&?f^xk9)2K zdcFx-fsMWxXa@^)JJx2p)~}G`Rn-1EtQ}*ZUjRU7Oj&?m$jI_UJ#==Ycs9b5b;12x z>@yG`_TdGFsU6y8L6gpL_o_N(ns>oE1s`*T6Rwt8naUnAcz(-^?;$(G<&oZp4A(ppFzix%`)*0%<2 z$?amQsfN*_cD1DLL#+<+w$|@FUB<~{G9HW!Bk-1W{vgW3taBGsEK&vrLYwB%DFu=Hff41A;>8DYJX+KiuGR4O?ZqN;H*Vpf zO2i9e7R=QoMfe#yhxKENT1sFsd}5^4mhO#jLMnKAZYg@G#*0>J(G|f?QuoCv$cg9? zbW2xoA}WZH4wYz{^$_&9Mh!PX`-Iww%U+!+hdb%K*Q7fbV48G4Qtiv4_BwlkZlJX6 z&=>_a08AHHXcahuTfb%tFXN21y}-)=a}x5wK|;vKg^;c438|+=jaejwyko0LNMwDY zsVMB(LDdl~E(uR~BQ6Dwv+#Qn$Ag=X{~SK?VgLmwH;%-{7LffSr|q0{h)BVfIw9+i5q{#hUt$WB0k2?UEvfj`ZTOM&hdep|n=QJ|#7Nr7yD=?;C@Xi{L2 zy};ovlC1%lF7S?3;Gz$06nMcUHZK6o$pgWFP+(L$O@W7{o)&duqfp?(E%Bw`E`r6S zK>q7-DbUTruf4rPedGqF*a#f}Fx{bTTtx{FY{vBx){u5_k!&@4FqYNkD-+2o{$D$G#Sq0!PL0vv=rJ zT!v(q(ky`K4t+b{q(Fwfz#%S@eF89D;4Q1b{oE5YDe$aIY@P*}K>@=HP<^gYU`3{; zzynfGi@J8cP~g%86u6CGaVe1Zs(mtfP^LcHWYRxj!Ea{8CEZ3YA%z!)uCwvNzGf%s z-UFDX#7wI|vAw|BZO#IZSq1L03QR#(5Q}uzxa92`fElFgq?xCSUr4v>08P4~QcsII z2}yIoJTnuJt{=hTl5XqFxD@!3t2LoO@N*jl_I&H?&{BYD3cO+!xX51MGZ)Dou?pN_ z6}WI2Dm9sBgiGE=0L-93H%);*_=Ey4h<(_q21`9$UkU|k5>TLwU~wt%)qmqs;0rGG zgaQw)wNYSK)JcKG0Miusk5%Ar_5z=}NH)bPaGO0q8PlZt{FS(r`1pS&CFa^Y)~uYA_#ePDC7!Vg zJZdkn+(o*3tpcO10%v0@+Tw*vT&n64fEkov=pt0!DUv||GbqtnQ)0zHp~TjoHBX!=_29k&YbUI#WGt75M2h8_C{vk?bz3z|~fPBWKwtQ0Y=6l>jp+aHyuh!T~~o z!**#3oFVnJs6*cp3Uo?9fgS{lOM&-aic5h-Tu%xQoNn*XhOJHtykf5G)I(N*BkTp< zc9HB3tH4!Of$VuU3Y_OsBTZ4?BZJt9yndH(|3Y4!s1TqL{ADo|||82Vou1^g}r;s=;P zfi9W?GZDd!wdx^zH4l_XJuNB+=>kB3#}iQC7=p#6z|t9UDbRqGm!`n2xCz_R1D}88 zq`-^j0!`g-6&PYKFxN%0aaMsVtpdSD8wJjBDUNdhW>DZ5O@ZmG4=v)xJY)ZRp4@~ zz-vgTX0=e^QXCZkGbq5z9Wf_7(N8FF{I8k^PL+CER7a#F0T1*@K!I)qi%WrpFT|z5 z+gN#N3cQAJWedO6o17GwZZ6PN*edXVy})c2$%0maORWMs;Ht4&7~oPI0{~`F09h+Z zfd>$`4JkPKdrg6prJfdb&}^YV^H=egSVs{oE(IE=$ECnLth_V@Rv}}Mh2JL|ofLS^ zT%f6tRbZyQz-um&-C`9OW)--JizCxU?C(+>{Q+iBz_60Vr0v|OwDe#QBKvQ>H1y-YD-9kv)tC)Wl6qRy0j~)K#wMV^;RK6Ifp?yZOMy4A^3oLO zXz$RY6_fS@6d`3P6|9_t~6DxRp3B-ftOt* zyTK}Op;e&3?rQ1lQXG8&W>CPe39mg}DDb9X9iJfew5WEogaYjoP~Z@P#ihVo&%~v` zYgj^R3iv*>4e9$|I4SUixzbbzTdDyzWPT;rHLQ0h_~r2sQ1VEC?w zmkI?o8Je&_>S<9KGlc^0ZH})AI}T&d)1~NE;JluvZ`4>Sr`Mr+zh;*HNb!=E zeXVj#o&We4nKcZm8B~Io9}g$u*i(l37Z#rMq~G%2q~lRbu(;Ut1U4d7Qm^F#KUV6c z3w-Sdw(;n@-Z>tBFxS26d#wBP0KRE2aHMMh*O|*h)nFC)?0wsKyyz0*7XfCBhvE8~ zf0~eIi!mOLNj)uU^eainV za$O`_3oxTYhROYPAL-DFAGDHLD)qFesV_^18WJ$rEP}=D(9oyjc4&}=UxgjNQJ*_I zR01&Fp`)z=`St?mxG2y8U`B@w+o6;0P=+x=yRi%;kACCl@AI@Cn4xE-p0DsG4F zl6uLbQ{P43kye2Q`#Q3{ORU=i%%FfVZ|ploD6k`{nR+Ldfuz9l7li@?5>Q}0!QxV2 z;uCQxaGTUi3anaYqrgL-IVtd0a}}!mR)LxJ0=KwGcCu9<*D7!m?!vHmAk!t*nE*2= zV9XmWCkq80`&Lt62bO`Pz_J-afddmz;B$h-rNH=q#ihVFsh1S^4w(%t{2o~Cq`+Uy zRj4}0DzL&{V6=;5g;s&XtO9|lHYsRziA^h3c?Jax&)Qamien;cHYo5N07!u){}u|Y z+!TN2Ttl$96u9&8xD>cm>Lmp_+BJ2 zQQ#Ms*!+T3owLV<@9P+&E|;!@!D$Kp~TDD{#8C)hg_ z`NT z07!ua(}e<;C7{431dB_7zdst60=G!Lq=0X+ZAil(J1KA(z%&K=TLq4{7r4$vvJ@J$>Vc&v2?Z()O{f4s3N$<~6gVaU1wJBJTndbRBrXL;OTDDP z$VD~^gg$ao;1Ymo3iPuIRM-oQa*?dSDsZq>;77X@>~V?B9<1`56nLkXP~ZeZ3cdya zDex|`DFNMYHpbTjD+v~t0yPiErNB*6FDX#B&_;o~S2-zgF~Bqh%B%u^wHFxaBH5p; z0v)Xao4M6&^1v>a*z9`NqJZH`c>P47K!ZVnEdU?|-hNgn@Ja#-tRPri3XGW&mjX9P zy`;di3v3jqU+JX45P)e4^tB31v=_MAMY4RWz(H1l4EjS&3jE*_n;)KWQ{crv3k9Y& zX+5wR0HnaYXM_S{5>ViMg1IQb{iXq!d-NW|wGYLm#8py1DY3TEMv2=$bW-9xfN4q; zSq0v-7r4|#x+ARu8CHRz2iqtSb%{?D>no>5y#EBDME7qrCDsCfl$bS5D6uaAB^n9l zqJ*ZvH4hqnkl-c8dU1uQs-g&uXh9x}^(O1w4_S#p{*0xR(OJU~{#Q@wY?qC?v&{ftXBSSD&VITvzUY5SFjr?8 zjES&B^K1>H>Fn3Us`3YneBeWUq3^LJ#VG0GK@hlDs`lD#d01ZG4Ej65>2S_OeasdS zr>3_-b^K>VhPoaz{QO)lv@=mXB2}*|Kz~BGrB9+wuR*?|YP|7>w)lCXmY3}z9PN5U zh#{##XaG(~c=EVutm8@5<?vrOD3@$2D!wh^e4Yuk$&_MCGfZdl9k z)>Y%UpUTSuiA=gB8L}E$ZMF6@Dp0SrM{8jmfMf{ok9dPiS#Utohx2VnhO)1H=?ZWCnRty8 zgX`A1k<7%@aV@F{P3U^mpKRqnCPbw8hn{+?C;Vir>NoYL zEnBqcj<$Vz5})+V;y4e3Wr&`(&rcD1_vPp1>;Lr{2i}r zr~)}8(3Hb;4Jyj*0#_RjS=ivPO0$=Usy}>~^T)6lm-$%b7*>~+$e^r;{_>WFee@2S>hoz`LCq@0!Ldof~@zCV>&-ytzeachRKPpB2Oi-AN ze>UEe9RF1Ewd1Gc^-oAq|F{(LJ2^%Dc`548NKt=biuxPwPEMbD{+N7xSM5(upXwC# z=cK5Abc*^jQq=!g=j8a~M14~JnKmgoeezP&KWks|`pZ+)KO-kO{#7aJ-&mg<|H2gY z@7S9h{|PDTpUXSElgwX}Q`DcAqWt4&Uy>J;_QlCv?1?T7Ld^{bA_@vllz|HgkL$A5H+`gh35 zmc;l^NKyaZKP1P0a*F!%Qq(^oMg4^->Tj5soIcqp>Rn~FmsZLS6!kaUo}51S;A-V$^jRfGR+8|)QUCuNf69ZtMmvq`I{KnPZdfzO4#166t0HiwA$m{FLru1Q2^rhuRcY4i^rwWzX=ja~w zu)W18To;~n+S@3B61M)$Y?p|hLsGQ&@U3akvq8>u+S==vu)VD*Tn|0cX)nj!zm>Sj zJU+kFq-bwWO&a~1C5aDg{TrUJJ!DQvx-_mxr@e9s^RTtol98x?52tX2tPZBpzX_ka z_;Y;1_IjjfZ$vunjg}w|TmQTX+nbZZm9c9~8vPsH(ADo6ZLO;3fIQg(P{LrP{KxR z<9C0;_KH)qSCdYAIc?^@gzYU(;hONI(_S@VLmc+R%%2j`b4ZHz9=jr@ah!d)|cY%}L>^*mXl1{hQq4(w|fJCZcCWiuUT$ zX|M1d7d?9?Y;Sc6S4Nj~+G~)cL^gdke@~+RjY!en^y|~0XZ72z{tZgl-mVm`h~l)` zlXGyk{x$7R)W7-^?Jd48js8vkpR0d2Cu~pW3rg$?8In$Wh3@uRcO~lI^c1d_t=Fc} zzYMoMe}BUEic_>#lTLfp^IY`op0K^ek^>|$Jw55Pmm{atZ2U6w$3*lTlA^tbN2Ni} zX>Ymu*DqmvTP3qc;{NqWt35d%ZtLI59}@MiCPjO5u1TYRtLD1;H#}i`o)qm>q|@Fk z$qHlZU(3!!{d-vQge0ct>XB*mFT>rx@d?}Ok)pj3>9kiRr;}{`^CoO>j${Ez+`nBT z(&%5|o38Pj`h6mLR-|aJKArX^xb=1Kgzc@C93P4M*Cn0ys^4(+Z~l%%{Tq>@z3Eq{ zLC*|#dxH|Tw@Wg3B<^2vI_*u9b38VFX=+K-zxovIE&gj7{VSZ~qUX&C+v}2|y&>te zw+f-24tuxtyF~q)F8M$b({t-pY4k5c5^~t+d4Iz8ic_>#lTLd(&SyBPt-bCE+gmKz zJ`(rOlTLe+abu#Ry_wq+(Q`ou_e?8J^ui-UU|5k2G z)W4b(?air9qkj`57l*BX!xOgWNzqSig`AG@YiUl@zlS9=NMd@f9-c=3s@(JK z_=N5CNYUPiblS^v_s^TKy*ZNiBXR$BU71GzM!VNXQ@>3_&x#c7)u+>5)hyTe^-kE{ zYRT%6xPM*JX)kAqYkfOEny7yxQnWYyiZtjs0oTeo_;XOg_I63mj>P>dPOClU_p|HI zCY7jv^(oq0e0duEoAqB;|87p$UY8W@4N0fHJZ9~)_pkMvME#pCse=;JbL(Yk^l#%U zuKwMhu)X3G?bW2yUIR1I+56W$VS9_4lJ7rw(rGWp-M^V%C!*(&6zx5HX&UsL)zRgz z>zA;-tzRbZUypR!D}UKFek->o>R(NY_U2rYM*j-k^ZW3G?RiqPSCLM8qyOXTU(1$6 z{d;&ra(b>FmPY@kxz}Uk6SmhQMSCOCYVRdi|GWv?oAX8T{_VOrjs9(X*Cl^bze+^U ziWKeD+uO750~-%})^Uo-c^}v)n!9P-=U4#a!MxA0nP((zcXGV(jP20m{W#yH4g$1w zXtLaOXwrF~V+&?c-1V}NW+^WOXs@>8P5k?Wo*N3Fbk0q-z0c92$8a>1gdRTPkp?|- zfuuo?hmn}WL5~-v+2|1_J@#WNO_Lr?1dViSpc$*_u>o)5)8nBb0Qx`DqaPYdLXQIC zkp?}ofuuo??O(X)@yJvgJrK@O%H^H~~A5=ZL>i)tKaYIQv=#QzHK+&OGp56pEnF0hp3#(;vb?_*}e-{)BGHJ*0co zT1>{oql3m{;^!_ruCn71Ay2%45+)vF_04@24mIaH+J6F|xaUc?FZVn>jyF-H36XoA z02y^=ReUB{L{RU$~D*osY0MbZ!moJlqfk2I|1u#OKNw zc;3U?K)6JeGB%K>tTD!wcxT+*{w+Bp;u#=N{B=z1+rj$V;dtW<^ZF)l-DSKmm`rz} z{J`ove%s`@78+jj+1vLYvK^K#hRz`OYI_`jWtJ3=K1zz znA*t?B?ezoN8mhqo{^ma$I`1FCyVehHXLCuV1frF_5gA-6v%zL4eS6}GXR*LI4Zus z)my(zdUZ$UAAL)gVE?04jYmUhP+#i!uPuB)DNst)(7HgpZ?;@d&ilP1WtjqilmY5^ zmc!Q^V)-d~C*nB@X;@rXzVsN6;{z;)>O(|zFQ;mvDs>3HUE|?oROPnY75`WxA*p2`^s`w+Th*#p}HYFa_CFJaT zoD$wie-W;{WxKET+dty=_CNYccY13N#7nf(4u6~p1mel}Fx(LP{3h2_>Wke!I~O@A zFnfdm0?30vyUOs1`Q~kv2kEar;cI0W857P$s2rdN5ZX4^T$II1%1#xqe(F?c5N7F! zc+SNeLj~NAoGy4%&Y}==ay}lT8!dnjDWVZi6W$mA%)c@>2GB_Z{5J-W3xK<000H4) z&z*P^Kob$qW#+eF5KlvNK}>7GEBvKj28Th{1TXTJHU-azvI+V{n*`6|pPXP{{>crV z>Mvaz>>Zo#pe`+)G91K#PO)DuuJdFFe>Ja~3_19`iOdM)p@zc-ps?goB5sm#^<0Fs zYLw%`n6}wGMayv>DO5<6Q!V|y8?(xnfQjUAPp|n&x zmX4I22W+$)`votN<2?1_1sXMEOhMgI+&OB=aZkXh=kc9bBgGN5PnY-FV0|L6jv*{^ zJRXGvVZjyFYg=5U12$aMWh@gZoBgFJ%dg>=A67o*)bqD*YGYdRmK zij6L<%M6l9J-Amy`=fPNw|j zo=5o$u7K>VUP8vU`2$~w@P&~??cXS=_MfX6U6QDk3!}%@Zccw-6G0>0MiURi|Aja4 z*YgjZ1t2{Z$?t$p;ty73p7c6CIGm>TrL?Vw(B7`1sePWdvIlA_yG&czeQ0GD#kaEO zaAI^?+1~DYKGt^jhcLR0`8iT{1aSv`d8*}X_5!~+TVE~e*mK@OFo4tEz9rPg-WC(0 z0mWsCjM>{Ah>6<W({%UCz(sX7BS!`?<@oVK^=k0`-6 zVjL7j>$!jnWGBWA_mGRt#lbsd<3mhj#g@H3o_(=O#@1%IsrM9I)FIFEp<-3Qa9^Tqwt3ldp!R_TmRv1hReWT_$$JK zK*+HlnF^>PgAT(%7;ud5D7=R1QF{?=22IrYV?t4ziJE$ltP=6G$6IQmuNZMBy!j%} z>fu+8nkjBdYN9!`LZNAfQPW^O|0UP+8-jkS8__e_LD4fg!9H5u*w*uQAECIwTs}ha zXPN7H%I9O4`Y8ujYK_w0&?q*4VP3uFKkY9R7xEa|3v|MXe0~;_gP6bY7wNH0J_n&p zOg>lf!H~}isg3fv5d$OgiTi8}yq>`;Q10)>8qS8-J#}`xK0Ce7P__J*6hvYoVpFK?Uk>^^x zCFfns*gcw*5zk$CW%#n_S;djeB;Y>OIh+nztC~_-U@k(;$C1lFMWY0n5 z%yWIEUGskve_lb3coBqeM)L1pCb2#Igbo8aC?k=1Ap=-ZE)K-(T+&ue%flpSxI#?>n%8%x=X@g}~nJimKmi=F!bXnHE2a}3rna=|( z%W2-RJQDu3)<^S!s>9ZEu8(Hmb6WOq8OL7MN0Fr@q-}jPX#!su>mwQTb%5rer?Ebo zPtZuWJmO*4zj%|t{w)TOo@RdQ`bgWqX1?io`%{l>pr9>OPk+h3i`5HFTCV?#urw&B zrSB8LtbB8alj}fCFmv&`GTcu+25kUwSJgb!CsWEu9$yr}y@3ZcMR2deSc#uFP`W)h ztg>`l@S*_oL049m{u1<6mVOr;5GcJNKRb8^|KtRV`6oAcQlL~B`=a)VfkN0uN$DY0riAdRu<&+HBquhlz zGL@J@v0lA)iZy^v?%w25$YW+gcmKy=YuX3C(4Oe22B(|P6gbm<;P*pa&snbLA7R_r z;kUS+KXpALb%Fzaw(I$M*E4M=d;Q!RvOfoH&n7?ak}cDC{CL26a?L_@@E82MSRJ-O z^W)K93O}w{7MCBF;Wha&Tex?LlKe@+kDu|lqVVGvJeVo`_%U2YfzsyS6>a&k<^L@| z&cG~g0tXCmn!T9DTBPi5@?njP??h!$X5IeuwdRq>NuzVUz$%J)8WUSU-*YO!N0mYczk~x>ob|UEq7pmkZ#juU@^X;&w+V-#rZnQHD5&>%#vN{p5hDVbV|Of3?Z?l)G&5{Y|&uFW>dZ z%ki={EZ=oViIVRJy0lilTjt+OpD6ylwo37DHuy>Xv;h1B|9+{7$v=T)@^6OZ_7X9g zoAM<8#sowByU*ZhhPVThm;Vj_bXXcD{@wkqjepC_ZTvfp=E?6IU)mur__wAy%)eBm zMDefoPk)Smk$5V*zxKP8AUioJ4z-;T!#6iYbl?~xU z^h(Z4FXL>~!_ldq^3A%GZ#G{!OU|KhwrgzPYJcAf2@pD&7lPVmYd=q?&Wke+8N2@ z_r3+#mN~-5$PZkOewVl?ALw!<1+QSG(LT_pNEyQiD*c}2XP~wiPR23zkg2Ot7E|Lv z1QDx*J!Faza47LXoDy+Drv3zZL#fm7Lqp&Ai{;N8X_fRy?gEo z{vtk7_Um4QKqQm@s93HCq`HwBb`3c{mRhD7g zZ@nuL@3d`{>hC#huZ!_cI;YFRh2j)hUBZz4=&zCf?h40Q{w=B>vGP1|BufW+9S>|& ze?L~@CHs4hNHI{$(}`cO7P1uX?{hGc6XP2wVDL{ZR{hI_u?rYSkd;A}k8$`XvJ%hj zWQx*D5j;lt!>|2zs&-npJ(UpKJ&n2GS;ejc%uR9+-*TgKlyF$Vmk{d_<83I3f2#A5I-3(0c+Be{+F z&sxPRhd9^ZWSZz{a1!$$SknI+{yDNN{M*G~FRQPA%1t)@eZvv@ck-_udBMMo*=86+Ih+uKz3|!Tiga6a?9;qc0+a{HD!ozAj6wC*R zB+dH<|2;WrUNT;Jk76lqLF7e)nOJ$$eH+A`Y=0#$Tr4Zs@twB^ULl9&MltbBY;0BE zuEMmd?q*Vt7ki3ZBVR6@bBG*|%V4zwB%`c(hoHR95u^Tj#!#9gvYp|Y4wsjMHj2{$ zhF3Da#*U#b-#aSs8&1eojfLTqA}<>(Ps8<=q=~wMuVC0BG2{eeXRb0pIsR~8*RR-z zlVPG7q!f1NB~X*z>hj}oUm=3x9ASg3{3@&^WZ-r|#KWxatq7$DX&DHi~gi>}AsH{QM>+ym)?IF6Ery6v@x+ZQlGhG4FUYf^vKupfH@UB@<0C zA?bByAc2=D-iHC~b_q}O{);As|K2n(T=Oo*i#P*FLH&a7npaIUx|Y2`16G9t%s?Kn z=|&`>Tn?|L)cx(6i!%``P*o|R^?|H($f-hxd_AoU^VnOQ#bD9`#v}(I6ue^+cQj&> ziPnU#<#dU)U=Z6)dB?B3O7cd(ah0TYvI`YHE*lXhtyaaTFtCknrMav2uc`U$3K@Sm z?NrrYb(j&f4__t{milDS2HYE#rO%ASw72 z7)le13+33GOXs6At)@#hI!(|2T&tQzqN5M83(CxSlABdOXx>@CffplVixIYZ>HeNhaS=)v z_}1xx6V}T7{*<;}?L7j}M4PH2@aOk&4Q< z0U$>7^?98Y%n!*MVg^@sv>ph9n(zcbeR(=m6`OP?t>sX)Ww@637N^S-c)`ieK{DyM zXcZI%rY>ynND`+ZA}5j`@)L&J34?|35^fk=hAPZUY1vi<;p@L33os0lJxYmPGD#^h zvOTQCw5pydz+u8-$_W&ic@BvCbHTG@PNJ}Wi~&`vx)MSv2~L%jrOobb6`eLNInUDl z1yB&s2N#n+xH~p1KEgNHBs=fZDvXjH9SECchx-BJ$j*0m**OfQOEd zaM97zOmJYVRFD|G&K_Z6Dz@WN9Ypy43Hd%5RRqENP(4IHlU*r~IulTT5uc&+;FtFB zS9pXUL(gASUK-3rsVNK2Vw-&R;$wVbpSsPHy;ZMlYAzd2S^0pe=)(ALD!dK3D>*fH z_4V!YVCSQ*qq3~#;$p^leazlgxhM^82+jK`CXCy)QEdT2%SHjw9W4v%he@y;F+b9) zR+d*7aln z`CNQ3{0P5@YHxEh?&S@>g2WhnxJ=SO^5IH6hWWtpGwe?Z^P>-C0c0N$;>R#HzZX*# zKTe9vj}naX{S01|T>?HFF8SaAA6`Yf!hA?NDa?mSAwHad$zbr#h&&bMejEH8E79S2 zTU-Bs)Mzc#HTr*$0xd=y6|yO$y+d2Ea*NqWROZfjbWNB$$J@-ED`4)V@h)XFwox=> zO2g$dK6}*j2YjBQo@)^BqWiiy#qxKhvGXRsb;Ifu$nYYcn-Dg*`5Cd|NFb%~33UBr zCJEUnCLe=ef3FwsfQBUzW*2S3I@1+ zzb)jL5ITTByq!)mU6HB#FUA(m?mKVc%~7NJq%Coc1>K4>^}v({DaNBZ+%$(hd5YX5 z?(gXRKfdWGG^q(0v~*Y62(7VNudkSnSN`FV`i_5iA+H_GKD9R zQBx3_?_ty9mow7$@mQFvzO?Vdz;J8$B5YuyUuH|I19z- zktZ~?S2L1h+2}=K{i6FphUA9am77?|CH5Zw$bH9S z-a@|DL#WGNDeno|;E|EoY(x7{8?Fttp;Y!YqYdXqwE-TaxNYbt+hEucA^g7(NiE?2 z>7D-v{NJ+SUx3N$a^1JA2upJi^`CX$5>98Kbhbn-1)%5A(ua{*QY>zI zV``{ngERMFB?HMRPLFRBr;C&p414LYgthb(XLFge#P2j2->CP5Y~azEBW;txBw#VQ~bbG!$JVtv4=)TpEdkFn1h%2U8ZJLX*5L!lyZh*6xrf3Sh0=mBt z#=>`y=H_DTz+CK*1DjQ+N%{0KP-z1bj^ac z45T(s__djR2o8%8x^`Ow|EY2N#N0!`krCQ0H8dX6#2WTf*p$=H0picaeXe|ej z;~w}G#B8iaIaV0xa5u0Zq~#K{zx!3FTtWs~zS3RIb1}Oi^jZb|o`ld(h(Jd>Isp{c znEDwttdTVYI(`gQ#~SJ-;=lsX7b)l`B!qrJWfbnO8j#l;xYyko!F}a55!@Fm=nJ4G z;!|dN1Uhhk2%v!bzl|D%f%}x9A>2tUab0|Cr4AFtm0zF6n5B7;6nd8MLvS+o#j^pJ|NXC-MqxgXA73{NFu&c9R2 z-*+@56oavN4jPpcAoLZC*dFwiO!k^IkFS=ClfD&t&8{|j)l%)@RYVD?{$59c?4H00 zlk}Pmse1A@-QOREvKs{Zx=HsRs{4J^K6?Hx&HFwcJpK-w?m&V3rJ8p>KluA&B3FV5 zcg!MBun6;V=?{5Jkhe|DyqMSJFD<(fdB!3S_T0vRSJt0-3iZnB6dXQM;lY7VRUTLr zs(COW`l6D`-EB1Q(J-j+g!${kte6JwrSp)Mv^hOA-}SA@UFpu>R(5)B^`zyq(0~EX zys+BUs=kor7Q2SfGyz<9K6qBUd1Y>O?#kc;m_vXP>u;wV)Jn?v2Xq46E(hH_Z8ZeSu|3^0)U*8Lb<>%_jQV%ykf)@Egj^qhEJ`g z9Ci$5?(6r$q^s5Qmy|uN`~DQE#^6hRKL@Ls%YSv>=X&6-G?CYibpG7UbGB+WfT`E8 zO@TX_bYFu+L}IbqQ3ktnn}Nk9g~bM7v5bDzFdnrgJlfaj`CBz_8o~LpgAV?Zhk*{a za?Qhcp6){(m!|2yK^gPfK>v*J4a&3{%YUw38oBf9Vz@RgA0GvwjsFL<75bReX<)0{fGlr=n^uYMB#!$7l zdAa|JTj1(0g_63yum9$eg;hUblWfh#wz)qT_WBb@qMk<{(pfPHDv!|h$~uQT|A+Fo#O$_hll*-K(BF0@x@BMg^~D~4 zAH5Kud$?=9mDPGt8vV}SGm8W|UaxF!D(eVqOoG(|qtitW;If#4yQTbNkFQ<~k)T~b z>0;e~a}MSX74ToxVeO`Z@MtsWy^;{cUUo8mvD!^9ZJ}%fXfn>!1c1$Q=Wi*0-`~fj z2f9^oEkIRX^Ico<7Ie42500?9K2r~Lap^T1Q#{EVk-scU@vjiV{zDFFL%3-X(h%l) z;D&NoM#!%#lXJG`Az%JGp+oL<$V!F`(`&vP_uxzHY0{Ua4Qd6iNb%0s{_1BL<|k^SiR7Qm$DKW!tw6dUtUXxT0%? zwdl(1zq&tl*e&QUoJv=x$;B`Bm4$g6MId{c?thDC4(i!^#g-(nsnUrzkH3p*A|ZjjCixu=9c(}eiSTlY@HEt+``-~1*FYuR5_*1pS*oZ| z>C$r%sB8$Qk5uUuOfb-;n}O)|!l#OR$0n5>v!o9^A^v+^dXIba$_}Mw59Z*bRPiC= zhV+L&wdiI=4?4yqNuucSc8DHdHn&OgS1Ec_Md5(D3atwNurji~K3cQ0* z6%@m4FxeNBfRQ9lTO>s2QFv2ZI-9U6im{BsF}@hRw^Z>(>?MBpW&&yS=XGU=4+*Y9 zlC&F!Sci*{yzRI#0laB;cvUUHdod9_-43sN3-AUeg4eXi*4~XsO4MH7hZUE8GwkrD zwgB(#kqO|H*x?nn0B?9Acn&+f9ls@RZ^uON=Adwtyu8=~yt=<6Xzxfnyn!viD^CP3 z!wxUC1$dnk!E3;pXH)Zmo!bI@IE?aw7At_ONcifPmz0>UQs#<`@`>^85 zi*AS4y#;s!6Txe0vbA?3k`lEyH4(fSc6d`;fcN(G3EEp?hgaAFyy1!9IqdLu{F1o6 z9TUNugThhz;Kdf;)m@jMy(8`L2DSi?_hH4Amkc|+)E3}%P6V%Er>(tjBPo%*thzP< zeq-(MhPME(DiORaJG_o9!0Vm}-kKe@_SPXOQF}L*CTQ;@JG}B1;7v^gFV7CIa|`fz zA68s>5kK47y9!B(+PmYL1n{QW;Z?N&@5My$bUVE6Ex;R?2wu~6TYEPmDN%b<6TzEd zhc~qacyC{wfPN)*c!e#%8=eTB!wzr9w#4n_eOPhjWey5Q>%SJ@)eTDkZ=@aGz!u<@ z10L5DE%TW|V?L81;f>4G1BIB+aOQakQ!qIiiD~7qp!i8njpVG+m_K(;1g`=4G1W4` zo6VFkylHlLtA-}PZ>$a82n#&j4zDT^yeu2MP8N7gTW#&_o(SF=^8jW0DQt7z?}-JG`lh;N{uioo<2Wu)`}%1Wz5F@ajqu zw0EQp-jx=3d3Jc^iQr|};2mOtC$`wy+c^=u2IRN4cQ#YP{bQOP-l{U1aA%UTid&YDdG0cu*2JUWdi&r+2D<_z$>xCo0e#(?^d*|RSs(GJeSzuU1WimWrsIB5j=+t z-cDg|?;5;CwYOs;cyn0Vs{btTCfVWDU6G)@BW>`mw7|==!z)h&FT)1!5DPqkgX*K& z+c^=u2IRN4cQ#YP@-od1Z`I`q+B?<;Z-fP&ZiiQu2ws*AUMCB@rtfU+?VbqU8sxXO zcL`I%?VVwVxAC$B?VV(UH^zXc?2QsTys3%c<=Nn!ZopIahQki8FcCblF}l5w%$M^F}-VtMzU@e{=c!L=|hN-744&(~@^J^dP@wxv}vH zvpKcuttYQVsj@STbzpzob#KC6PXsgFasy3h<@;aX5G;1JusXH)&82T~8LAtCID%md zro*f@R>E&VX0ESGIs|`1AWlfK)Guj=h5yvYFG(O^=K+YdgLY<0SYX@9N-9umQq=O@ z`}!O#I`dCcyS(L2(;W~txd4yoF^9*09^$+_**o=`y=@Waf-PI%ID%^C;0Kkg(0!Y* z_`C$?N$Sa4UD~5dJlWe^+U%XkqE#`-3aisvRa@kqCE`n0>^?H^@@t&PL`8#kh zId`3I^Z2%UvVT$W1_(W>#{ad5Cg|U!*SolGjD=%S{w*SxFK~GBH)`IOkmSzZ(m&8X zGaL_blq^@z&;W$$K(KsRAJ1hP1{ld^ZG*GPjx(y?a zQ=CP<0>W4AjhNzIh|LZB8;?UAbRR;$b%d*l)D7eS;?@`pj`(JGa=jjK zIf6$br|bv1uO@geUi1Ylj6V&bI=a?xi6_vrj;L;a9l?T^o`a1pPny)#LH?NebK&~d z@@F_o?gRe3o&)~uf`STn(u4iS)PJ7)sAMI@L=PGEsgZzo5-6m6h?4t&KYz^ve?EhjJ;~; z`e#xeB`SVE>8Lf0{pPEq^*M-zWSTl?ndLg_b?YpIH34=EK(VrwS$a zLI3&aB=F~0=*olqiN&9V@3)pe8&Pr}@aOCk!JoUKD-ZG~7Jp8DueJOsM9F=?pT8at z{(J^4dyqe|_%nWKYx(mcO6~*xG#(57~;x6JU3swo#OgoIwXNk( z=SBO3KckKUf968V9^_9f{#;YjTK-g_AEkpo$3j;g=XW6dN}y=By{CL{>0+Xg>zfWpW!IE5BksRhk`%5-Zk-u``^Nm zqrB|EA*Cor&wz{3(KG5Hdv6`v!_K6c^_k z)g>3^?(_s^IxCRR-^a1}(nx%#$Jeu#=XB*&_QA>cm1ENoMXEAt-d_;*DbED{(TzjY znu`(P33vcdDUyAC5H#A&sd;r-&j4qR|Kj#I+wKy7&uutFDOt~7HfFjWxHHW-tZkk6 zE2zfGA7XVlXbI87@-7CP=;vS(&Lzz8yAWZDW2g9@spA?2yrww$hQpDHan2&j-GNi? zJiMM^G(O^7K5-?6aKz2wNOHHYo_TTNE${|JalGtFy=I<@Gm~(qRTmJOW%LSMeI~QG zH18-hfQPPEgE&0s&m|9rM+mLxfb2zx!M+P;WZj4ucV1{|++!!Natx#nK`PKL@b?4C zmpasi6Frmlnx+&PcUx6`e>U!ObFbLt@hy`lPbDwY^Uu*LH-Uqm>;^s1KTQWU!LjkH z8OxjhQQ4t-er|b+$A7m&p2@pQo))@JxH#$N!J+HKROoLWz#h4jW>F?j0el8PaWTz; zQ`#o+*rOzmRtR}$6y?lMy6_J276jTvk1QessBtmYkw ze{hzwD~ZSTspEF_darX5l0Eoe1Sk&v`7%LXgm*YA<3)IrofXIszO%0#(*yS<=^^Z7vkFO!P6_16vKgc8IQJ~jh zI1jaLD&PX6X{hW7Rv5xEkh&=qC!6BdhUrGKJfkXLGy;96cmZ%ifxhx;nJ+G26LDzs zoHp_x!oJSY;v+IRkopO<%sg{N?X49p*xQ7P9=BAqd8)W<7D~5Ogv4cchq2W~OyeCc zk1LWTe-uWza(DWeHRzi-e0viP;U3EaxV1{t-IsC<3h6eU(9Yc?*rfFV&ojl@nmEfX z%V-de*X4yGX=2Y?ZE)2AQspg5i^MR@n{NWa#k>wkyeUchKM!{r+zX}9-Mgo$S$O2c z`eq%?zN|guy1q{-GJ;BNO9pc@#UTF`CH_9RVzO47I2Rwd-vAw#cV^`8(7f--yu%Qu zj-v>D_{5V^BL~H5RsRMu2x8AONur8J52Pla1u|3Ma+ea@K=)-ph%8CtIeHnx@I{;` zJI?^twp^QdA%P*_pJ%Y@*)=#RvstUUk%@r;hd|Qm}{fDs2Ro_aGpNZ;bj zz%Bv|9l_z~?TYSgp@f2K@T&Hg$Za<0WVzMBQE0Dt8l~OuhV~c)73F%J*9PP6gxQp3 zC@XF;z{$3fQHfaHD1fU_Ve&*{krR-S_$qU3q^ZnHdJ2ZTk@9HO`H<#{;SX6c#-M4lghegDhz zve*8IJim|9c6t6wGgRN3(3J;To`(S)vpm0xaW9^J?oa(ZQx1$$o^x@6;(^r9hxUk) z=Re_Ml_>o@?(O{}&r`{^124}DX6}D^-u>@CBG12}v|XP6_8UayYs_B`v^)oZj#-|6 zfCZ)Gnb+J{_481uL-D1wp`|=0;}G5hDbIID>*v8c%e`uSB1(ECF_*O6@pUY>va z*Z!C1KcNm|(_$;^jV{E{W^dH*f~cfJ2OMa5es_jdo=?Pht>ih~CeNirvb`M8mA&yB z@I8R`^VgW~SnTJAwwdMmTFUcU4AA>So_Byw{{wkG>E-<|&x2q3Bl0{HrS0m7_XH)A7+#1(qDk& zEpk9t^4xS_o= zMCBjQl?Pg$i-3+pP=n>?Sk2}nLI2XraV*KRwo@_ZwLGc5MTaX=_CAH0F` zd@Ba%{b6rZk!=TFo?m-*|I72HXa0yhZ$W9hJWu=)qO#(j2VS1X0UfhEuf>E~$+O*G zcMAuR_mRQy*A@JHVCC6;Zj?Ot|K2Rm(qH!s2I&1E&u^1$2VS1P`uqNu=i^XE>*s?f z5JTI1@c%YLRFa_s4zzxL^(m`7cffcZ&)%525lH@q`K%$&D{$=Vft(LcI5|q5@A=j& z&(hxb00Z>?kmrq{)Bix8k9czb%X9x}e?*==C~cSL?*&997dl`c$g@oBur640ev};l=WClBr#`oT z%i;fi=Np3a{R#5KO)Col(x(9$=^XlZbaX2wHy}=`fug;Aq>e%e$#-HQpQ}M z4>>c!^7|y^_ZB&TD|`9J10%nm;gA%IoxJTUv-}Qa*DHK_|H$t!vgyFf?}Lx+fBCI_ z^pD8zVwASa?={~*ME(UecmU;h8HQjP${~t@lak*Y0;{{XpzZ~!W>mQNh-%#2v$A4Q7QTh7uKOo1U zFpd9SAMeH-S;=z+a8vTkb2e@IxQ&`TC6wn^F%a($ zd9EYd4!k`77})>v{3q1W`uScLVrZN1)vtx9q(cX^S}%v>ITRo4_I+3|(Cu55yOL(| z&$&C*CBqZ8WFSn^gODazyfdB4Ap;(S3YL0&yrsU;6lCXhGN3ZIS`3ydZ?ekk+l??` zgkY{gK?YGW=1|O7v!^+CCyILz7d(d`eY2d^{0$)Vi;1j8^Vb)84`zcUmL|_X7jgqA`n(a zl_K~HNB)X(Osn~h$A^%^8VWmS2hITYehxek3cF}$8*3=+O%h^T2Et0bc0bG-PJABu zfER;n9kEd3dAnBBexhn#z7IQ933;8}p;u3t=Go9Be$xdW*i z*ENOWO8Yv;9!?-=D1rr3mqTMQqO`(D8!bby4gVH`v61ERqg5EGD;!2zEG7ZH(0#SY zQU&-%I4R&$HHP3ftyY1gVfdS$6~{khif&i;=`v<+-^uS&QpF{bUq{`4Ex&~@0Hfr0 zk63^}aw+qBIjzim+;`&>Ab`xOZ>BERQL zyiVM_UE)Rg)nlP%m)~4gY?fbF1gY+_lCb=a07ePq_ZXRbU&(K0C`%(QI!b=iF!5+X zem{XzpW#~W%qUFXxR)~MpSSf^CEpHcQZg4;t|?UOTPx!xZ~b& zz=b|gwUi@(wh5ufC5uG;gJGOP@uw<|+V?Tgn6PD%F$U1kOKim4wo#*JsK}F-qxQga9XMd!T72!1mAEU%6b&>_-8tc zC3vq>;9UnoyocH2+vV}UplU$)G{!hwW=_H*lDdsHu>x6&^0#P@_jPL8>;X>Ali$-> zp5k3ueyO{%F`3(8IB>~0VvlbxoJk(u;9Q3hX1*NCWG>$eGM6}qX!{Cr_W23U^5qz{ zw4}17Ktv+cmVCxQTY~U?g&}vZsErmS;%{IGh8TITmKtJSgjOWOM*_}BJV8He-ZVB5 zJTK+wR|4vYhmppjti^SZYLA~<8%45kvj7>#FQM@p2daxA*wFaI$Lj#g z?OSZxGBV{y>Y0Z~(EK$a9kZVGUPSc3iS_)}<348w?sz-Z%QUZ(qGB4P>X~K2VkL6s zLCQGqADdpVWd%qRmfQ&>CRL$TAv!&7w5m&Ouoz-cnEP$;9t>!q{yK;I7UpqF1IasD zj#VQ-MYC<27JX96JnjgNdq9@pl| z&~_LWtKGh%o!j+)1BGG4v%YtR+VRmFqVqrl3;7-(vUqD!Z&H*3sm&*}O=5g}!;;>a z$sXVO`Ue){dkX$%z+8GT z5wb%T8Rzh?lUux4sd!yw-26_6dhfp<(of<29>!G&B_7L$w!ToH)jGC(4EC$__>RSszufM1i-yraZ zTy-@zaB1GZA4Qc07{x`X#*w>Hd<}8q=7ltO4;Y)8_ePBx z`*lcLG(3YLiEiFE28NVQ6OeQ6F81~ zAB5%Z8j*wyMgIH4gdP2bg|G!fN{XtQHSfQUHQ_oLdz{QfeH?;e#x+c~)1t7cgnA{L zqp+F#DX^hCN!)=9W^8KLSh2aPV6a5zl?)R)d(dmm=&X(6aTuM)$6JZ>GfbKo=y;a` z9S6~&{bxp}(u&SC1&TN`k1=60Dhiu3;$d@06gDGbV{-%S71aj@Mq$(0$}08&aO(9= zCS*4JY9-AbP)24>ndH6SGLsnHbib8S%PhgPBq{erjP{ ziDKLH9Zk4h6ouPskVi9aVYaOuXGNx02pOwP*M9^;NfpKUxfz+st1QTbWco$4MQIE4 zzd5_Dlxl)|-i(cjQe9V@DCLSmZaOUW7&7%Cl)^lQBjA{lI~Uv#BoS> z&eta7QlpR?j=E!z?gGfyJdW*RKooMHtgs+wbb$Lo<}hw>M_Y8mzYua~#w{#lWAC+6 zY7ML-CGn9>0U?RsNQtjU1~VUjS#CkbkoXA@$S^VkqLBF+BsU{t8jPNVjG094xQ|S9 z``{ics$D~ucq`-hR) zyTgji-b3ObGZj|7w5<)JYX@e%O2lai7*>~|)N`Or#ka^{Ce0`BTaYnC{5JH>FfzAC zA>)pRj3Ww}ks)NPLR{DY%5bGr42nYLm-j5lgoJoB$QZ`vo1d+uc?|Q6n0&hDE-Pt1 z$K*Q(pT-g!X&Ed<29gGr-APf1gyeS&dQ2FTtAI%nCUR;UvOE75&3h$=pCaFR?#g;c z5vK0a6g6zmUq3K)_pioSal0TkZfh34_EtIeuu?shAs^5aQ# zaTGRhV4fF)Qp?!gX)B25kijfO%?%c842k~CJfL)mLgu%4LbRydN}AI`$jpPG5hc-2 zfj;8MC}d8xB4bGOm*x@VxouX;^ov5qq@`zlVq)7FQMgUS)H(*C?jfO0B%#J2gPBm@ zEwKJ5l}3M60oNCR+V^rxm*)v9ViI5A5WurGFH5$JJSA6%z49<{{)$_|VPB?Zn(V zrZTuS3Y!lwS&c!fd3C@>+MDx{!Az?yi!9g}BEG~tg1DoQDTs&6`a7(oafOhHA>tbr zLB!9FLPoP9V~F@t^9VA((Mp-4;fXO*#$=cLR86}CGkKGD=wkF;`YhqJ2>A_gc85F< z11BWFZzcRMvJ}>Ua5JOkorJcFl82j{gZJafoUigRbH1x#^oktj8-OQszP~?V&Nl=0 zr*JUevv@M+J8GIa-*%W^VtoMlf_O6L^S@-ycLDRwVZLrr`JR8;oNp3r5;2AO?#GiE z-WN}q^R0ljAVxA@J)X?@K7QStZx6;!;bOk+crxd^_)T*@o%u4D?}Dg&i~eKI=f#*K zwoXRAd+=n2_s*;4d<&s&MFaD_jVE)yJExiR?Sirq)0wXkPv(4^-!kXx!+c|zFFz{Z zA`6|zLtBVq<}1gO8Q$^J&G42{Ub2|4I!a!)KWff*H1j1f-(gYt&V1IKZy3Z~tnnk? z6?ii0^=%S`|4_Mdq?G#MWfA{jt_gTD{Ktko_pr#pTFm&xBF>^y=cil>wP~E6k`q2ZWft_VJU_*)&QEdsu5=aoZqtK% zH#av=K|R@6U$WG5s->P1)>Ce&XEijUtsa5V&8Vk-3s+)gJ@@Ou)6kRQ{PqG4bEJNT zbABxK{pbJ8?UM0xsh0W%#j5X>c=ctYr-z@|(X`aJa<;jBGETd*rM^M2>U%a`eP==QnCnZ2a9irT&Qjm~ zc=i1m8qQo_I#$sw z?R&sd-ypVc6^4FO`!JobwQm549IJiZpaISG{bPAleMj2s8*Zs@P^|ho#j7s^n$%q1 zWsD!-_8A6Fu%G{PmYJT{vVA*lx3mxQDqH)~NaR@UJ03dOTwii+RDI`I>XUJQRhIe& z#j0;58y%}Y?FVapw`1S2rG2$;ncH_G+t<-j-)hXLZ0&Q#qh}jv8*}^ariZ{x&&w_K zm9oAUN1N$6C{}&n#iQrXo2>QC#WoB}eH;E`Zr=^8ue+tb)tGwN+P6L)J$DAJ_0`lw z)pv)bJ~=Mbjk2^aR(=1BSKlTql9=halwNf+J=<97ljFlcOMR;`m$9`kGhToH;d^U+ z$vC9bQs1L*n&~O!bK`B6_Qk4iLp*tD{LWfm_Ohtg@#JOe zMpJ#_y@4DWFLGgc)PvdTQ(u4!q)wop%}nQ~-Y~aY>VY3_HMje@SnXaAkFPzTcR1cB z#Muj(msX@sB`rqc3~2gUaa;liyAME#p1lBzBZQnq`VBY z)b~9?B5d{5QVGSX@7<{J;idyG6sx{!^LSB&2|D6i zcI$Itvhq*!WFx9)xjGTBLG!*!*j3eYlTj^B1Z==*kJ z2in&mH%NffAaNF)3N}R;j=11DqKzjyS28^Q6eo=q#P!tc$tdOmCf$xH%C9fWDax)# zc$3Fh>rTcr#DyT1!}Q8(L|fD%j~+fZ(39U({w+T+9HL&vM5r?ZtIJOEWH0dmFynu; z>=dWC_F5y5S7Ctk(CLA%VnhqYOXB>%oS+MnABZ^hl6b)A1 zF3b0*_i8u=EZ?KvtJM@DjE^hoy;_<4k?&FO)yhPVe2;psRwi}id(?Y_QfcAuZ(vp5 zjBifV`_?G>jE#Dqg2}JBd|K4|lqmYAh2Q5uOTbXfZ4RzLo#KOQAW(BbP5zA!K7=$J z!a5QPUHs<;d{W0b<{8I1<{%CX$2o2Qb#OYR`8Y?9)~6}BjOj5;WP;wYN;sGvv&6HY zaI6v!GCgLAFXNP09Jj<^a4A+Dy_p`fj%jg9RKzXO37n5r$6gFNvFjKRr$kSt$HeN$ zIMm%8mmkG(O60}m$A@vKJ3B6Q+sBcTpW~LGD=8MOwqXE^-Euuni5#ZKq}2t4Ws_YI zJUwn5Dd>Cl5>3G^&{47L*oOWTtHkF_kBL13gWu0I3>P{L)|5DdEPA!&rgWU z^ZwAHv9LOa=`q_uH*KsEqvMu1Bo0EoZO6(uy>?#QUYiz2+M7@!5{t*D z(Ebr_uMTY)5g6-r|9Bj896o{>TRD)(Qn%UH>WqV}=>2?(cFXKTw7l#L^}kwI=`nZyVDF@AK4dX&#IjAtZQo08u!J-S8S?TpQ0m{xmZibnsb@67+;>2 zk= zn6-%dgrn*uNH+6PY#BYy{Nga!9gM@yREKCYhC|DWAk#|e{Ys2FkeQrhr8Gf;1555ekW!aY@~GXFWjm8sV9OOETg4A?VN$n?4DZo z)6Qx>gD}KY7BPqvdUhG&^1OO`vfIDzYU6MN=dC;fejMw z{3T_W&HW-)Ux9^d7r!HzdLCZ%`a))B#pMCrz{r!orwmHEQOsZt;O!#bRn5>Ar$f|q zztT+1p2aWuo6EXR(Q!eC#03s{;==75zy}p@5MZYAFT*9k=c9B`Cy&M9iq8H~bYWeA z)bi)?Q&|lmhB9S2kjEjbuP(zpV!j+wB65H|8e3#8wauYwW!VT65J&=dheWtL0{FF7 zzOE}#R9Mf7AYY#RZy;ZjF9(0U&QD}jns+L2#OrdravKQ8A7Ic!H_q(O{le|<2|;cy zJ6W%6ZYnz#6jEXl7@aPXP(!E`Yap>c!jxK^p(?3X&@Lh6IpXPolnOw`a=r@$YOD83 zf*A|Qy^_}B7u%Qmg!;_A<~~!oC`->@TmAv?BSbyWt%BSoM1@PucWp%()HO29U#S@sdunhn%IcLDXQc#-G3gasimh8vq`k(CzUnj_-U!ryV(Ma?z zfJ`l=ZkLb8@tb8IkvV^ESfMJRbUD9Hwd140%l`e5JB=B1#Eb-z!h zvEK)mnV?g)e~|_ttP?z+^|X2})dNi6ScBA0<-VG}&XMrF%U0%$K%-q^$w2JxdE`=% zwpH|84fzK>r=gt&JvWlVzn`8CdU8+E zj)DQ6z;zBxCn~pKtcFm+4=ML)n}icvz#*Ak>9FyaK(E$GA>`;T9BdcVFE;xWU5!T+ z#yv%Hb9WJX3-V+lzxbM}S!Fw&*)R}cqaU_Z)1?RrGqy<{j(DRi)lnCTXNs)d$-Ey) zZJERkTOcQSiGWS`0@<@%wEvdrfk9ijQxbNC$7#NwMolQVUkTeNrSa(R=Y*BGjN3ZX zs;bLVa;vd#2wNOS^A?*@0xy*fl3UJ4igrVYewlMD4dBsY4Rjsv)xrf=IH<}u6r#+T z76@4)diDeXd|&B4Y=5s84}%;Y)CwcMo~VJ^*f}WL(em zukQPpgO=>@7}5Ca!R%)mezl1;Y(Ivq6LWV4+OI+qR0t7s_-g5ag@-o3;SO-49-BE? zLJvHx1{fkB{BqFfFqCy8L$%B>U;Uzb54e=&_N``h9F4Sz51~5lMydK5uYIvC%J(^p z+@WN7y(iFd2vqCN@LB?aG+!--4n ztYi;5)5Bi!xukm-|C$YLHShVzn!8gR1`^1x!E7YR9hqDY@O>o(N^C|O<-S_V4`^3S zL)fKh>RGXS3vJ*=?4A{#Q!I~@@wLc zo6mkKYiHmN-Ur(N1<~Y=bW*9G2e2C7vLfGN^wAw?6*WtaMVSCMSmYQ0;Z_(*0f&@} zOQoJJqu6}r@qH~7gC~%haSG|*D3UnTQXa#+D?b8)kVAbcIk!mMYC=UiPW})+3t?$c z?xOH%!sb5WGa3Va3;3+nBc%D`_?#UZpNu-{m`1Ulp2NiSDcYy_JO^VZ27Sh2&m8e7 zZW*6<`|V5mXtD8Wz_vN!!&RRa=+kwd;xir=OAPun%~kx8Uf9I^dgh{iNuNfH_%ZOw zny>JgPJeM?d^*I$XHb!^-Z&B@WaeVP`aPCzFZ5TBzw4)O05Kny{~E=`)X~wF*|St) zF)iIL$X?`cSA?q@VU4-{N9g{eXz+duo5V2BvXRAVo(*~(=2@X9U(5kq9?Y?x;*rjp zF?Fz*XIj;LtUJo+l{XklJx@EWwgo%w=%+ZgD!cc{zpCd_i{1MbLV%>57G?K-*dKP< zlYLDxp!6BeouOvs_HSxd-9N}YkAQ}x1<+0|rE*k&VbULt7#Ju1BROrtX`C{-=T$3>zYFw)^`&H!n&!=+ve{iF7ayL}&o1YyFK zkx5z@@cgP@UY)7Kj^M0PJRp_fEOk32 z26`3|MUZe_nkP*$nju($R;F07c=H?>Upz};6HU8P+GBg+cQ;P4DB<;*I!;%}BLzyu zW9++_ly){919UPL*ER1?slcg6$LsJi4|Q^t9sWhbG#sjmY2NFZ3nvJ9{N2S&3ONpn z;Aui0+^Xokk|gQT@od7Ya`Od7`e*`^mK9Sf#-5d=`JRw`aKW^_kJe@}+C>Bh;KlH4 zh)wL&Kop~KJ@VYOOc>|3AjtN>{G3Mb)L%uyvY@7V^9VZ2UKO`C|4T2(q< zDpeUUL7APJ_Yfunqy!bXcMnX~h-FIqO^lU^a9ZKT;MjDgXKH)V57vp1tY2|bGEKdc zx!ZUl2$k`|sEmByrlrA1-=+<&#{WyVYb6cab*&2y4*>x;Upd7>NbhzAl7Mt0 z#kcnWQF-?&TO!eU#Hi)7A}cz4Cw#)KmT-9lS254vxSCo0UC-vh+ySpVM1`&2DjWJK z&Tat~m>YSGl$p}aB*vakDQgy)ERAAx>atiSe|?7c%lE0_e`a?D1b!437$v`>Q5fhV zjeF=xo#Ezk1>Nlnx@&gfF337iM6R;oLAxBr(}33Ji0=63s%3VB$APV$nJcbbk$AMvre@kq>$ybn1s{x`)%K zE3Uk}SmI4XHjMg?)-Xf;4@m-3rdl~=A+&??Yg}9ipxS&^r+CPEZu(}5AlK86EC7? zx#24(VR3LsXi~-X4Gb1wRuMGJ;qs83deP8}lr!YlI9+D3ktVwW($X;9dPAlu4Iehy zwW`6OAO=(p3#RFo5zhl!Yu;vRJvtDibb?=)#EZ)TjcfqF@ERP8JdnR|f7dH{5|38Z z0h0tV<^p1G^#wVZr<`(_mY&R%myx2S7jS_r^*tQH ziswwd(v{gQ*bBW(j`O&WGy~==^o!DifR!XPSxs>!!v>Sm1j}c3n#z+6<@_oJ=v9e{TWf{;%ap3(8#QQ zW6y`^_%RL2($}NIx4%FR^xbfJ-`CVAaMU=&afX#hmraU1KdZb0#3oD5!lZd1dPHiK z^@Yxh!;-b`+XJiR_Zf$oXp_g^`OTNmAR33?i?gVvqK!l8{|LK3Lzkq}mei2F$bT-> z3vZ7fgDx_B6;m!St^ogD(lE6IiMl^eT>*ZVyaL=4NI8U8fKS25dVe-uEX@1A@1Pe;-T&>*-&!yS=}Ei-`~qG9ei3Z0qddN!;69>_IgC0>Jw6J} zLRB2O0=)VKn5CC^^4CicFiW?x5%);tZ8J-ARWEA5kH@#w)RB~)(Q#k33&M*5Ve?$} zlR&Q8-BYPQ^FSqbp5Qj&8Y2WymCi?GvxEP>b2MN<3(cI0J>KEK1}R+y7?6 z;c-A;EDqnV{W25H_iMLhnLw|fKV~ZtE10?sPacd2z7(g}Nj~~hoWZJ)JSXxSfAVk$ zqd@A^htzYa#eRNv5mICLIUlBuZ_DQG4Bmtmi=uoa@cwHoVe7p7npQb)zWxe)wBCG; zp%?C!;Ggkh>OxYU<a37KC|8S4L zkD8iw;=JrWOt6i48NXZSWefj-4$v!lUUs)Jj9BJnh;=I>;-S00ct2u^K>VN60AIf8Vi( zAvriYk0-oxcw%<`X^5N=_#L5rs!UaJHpGm+!T_+6Ga)Ppd7Tx=BR+v+UmlLd)@F!R znCD}52tK0iSO~;4@os3c9PvIsKxA?t6XGMh3DtPt+E+n0g zG1ll2<0armF;SL8SPbML+`F}^w^_x6#S|>NzOPSlV)YiwnD__suMXf0q)zkE$WVb& zA!b7vub>*G-e=kr!VRQewV(-0=WI`e*3tLE*SpAet^In-sSkAn5kY=%xP807YS#M?q*orRo*meLG|qu4k| zQm)AWhcs~0WUzRh8OVyRQ$$4y0g)vUH`DPez8GaXOqP~0r^U3&%<&*!Lle^)%U6o~CT~C9oc;yr(H-9SBPFPzlm2msUat zz=V)T5rFkdVu&lCRKhAE)U@!|jx-wmf99EiP!7Nknt_~t7cNsdZNi9SoCKXECUs}# z6R!m>Xgp{1Pe3Kw+MjN3KeM4fJ1`?d6+FBa&DJY}Y2p)*z~}{cBbRktc=_)b7Y0Tz zr(P?eDb|~M33-WLqGV^isiPSFx*vjzrP1LfZ{>LLA88+@-uhtp+**X(;af=gkA^@G zoT&Pd>?>h8$NWhWohr|TT%BO3lW@i1)0a!dVL)@lS>gDkKx!)LG0v}5^Txa? zNIewRSLjena;E~{(1E7r7#)cH2iAr|ETVN#xfuQ}`+1fjdplO2%8)w66e#MDJ%>Fk z)C-Ea*hd0Dz&r*HVmmtlI@21Y%aMuQOwt-Xi&POc<4iqEX}DR=YG#L`t>7;@mn?zK zMKJsmf0ttfBo8Js=G;>5ibWv;c+KL>>=b9&HDIFPk<=kHaX0#WSh7QK!(V&EguhV+ z^}_Oz7P#!x4)Ml0Qm!9)DlFFvx+%G~gk3zz$}N`8%g9DyhFH4;mr$nB5Rpz@q#j=) zoXBzFtEbWbEZ4sCV;mM4(T~8#GuPm8+-^*8*2@(0y$3^5TiTnHG$=Bvw$k*&ML-Mtc?SWo- zN~$>EVQ>Q*4dWne58Ql%NvaxTwwCMxA^WF&u$+P!nOUe;woY>xj-mEt2#r8Gs`9EG+q_i!n6cAH~6^`ep<2eMdgj&mQU z&9R;c^++v1g;coV6YRy=LKABdtj6V1gBES9w<`w z0IcF~sSZ@Y+KtF%wFmz8SS)+sG6}5}_CS_2nQivKFxarhIresYpm6-}u?G$@cJiC; zf!$!E#U9v!t`gTCxNS^Kd!P#DjkKVDpFI#~eRd6{CAJfQekIt`&!A(%3Ggro4wXfB z)W6B>XqB~qk^&2Yc7SzgoXqU75GwG6r!kA0A#6Bt%ij*%Rm-(E0p5?zYPrbx5j)Ut zl`k3w!H;4DpjbuV@kby6y`xI&vyM8T|!yE%*xt+!xVn z`FDl&nkjDaB<{cuX*kp1c?9qgpPe33Zs3^o$FzREyu+U)KVC!Y4SpQoB?dp@ub2M@ zhQ?Yie;mW4(cg9>3Vav3(BoAG)2;G!-UQjy~(~D+xQBFceOUwYh{#;h&RHGEXj2V#c9O1Au4_^vX zk!upF_xN~FeE}>F?j9J)FaG(moM3qg&EA)0e=bBV~yqR+m6I^*blQ#hIAr? zbGLZs6RuTZwN?31L=TKYShQ%5vmd!7j?AI{dqW~6mxw(G1i%D~Dcl1^p%4R4kWj=P zR*zy^jbiJKOoH#>C1R}65&}Pp<5fv_U=%h$@e4ez*5m!4kr9`V zuvCD$vsVWr)j=q*gCvSve;o)l}rd z1~?Qw{!*QgssVxF74U?)FkxNa#JZR$I!G+%OZSCw-9`p;=9zOgFy}W=rH1?5#L*?v?T0~3&b-)MQWeyYzw~4<_+S|z zF>xzuWkjs{t>?!q5s)(9>;B2e1HaF+Q0){o2LM&fSDsh4jRe)aT#w~uxMI}LwO6K6 z;0Tc&Vl+zMgr{9_>vIrn#OMd5lVtpGHpHg0D2Xf9Q()x+EdzDLWhm~JE06Fnx#0Co z9sdx`HXjcj>5=-mKVr5uvASF$3vMh2EW>M|Uc|(cOw6U^JFqmJAx0zF#9&UX#Zab! zf@;-D`iD!!%Q=QA3v|R&c##XmvfGuS&0-J`4;#jyW1grAl^|JlF#@ll41{qd-l<2? z^T0{?iNydT#l;p#LSmjeHbiStC_lq3jhYK{fuUD+f3emmrW;y7qSGJ~v675SDROrf z%mvAT@rH%`9nlwkJ&g=Q2P?*}{7QHwcoEOADDSnX7quj(!5ZQUr@UEm_ZdMvx^T5UU*iGLdq4tdhgQz)q`Li`rS+Ocwa1Ul*MIfGl4?)f8y6 z`<;b<2Ay3k+A+|1cC~)}N}W4d1AWt6?!bU{?XWb9VE7FPzh0jT;edgKzFF@SNo1OBaToD!1L>~E6EO+yeX1Qr#0 z{I@vMB-;#O#4v)4{8l2l?RCP(gZGF>N-_`c>@2Kp8ki~WfFf=4W%X4pDe}WYjA_&@e`#buCh|` zYIY^{o|G{q0mkwp=tX%JZ_wH%3BMC{$VK*kfZqsvx510o8un&N_P!!F_k+E@MmtwI z6o^J?`$hkQiIegvnezQ?5^5MNU76Is+uy?>rWV7;Dykd0g- z-pb@}JXPO0g<@c%UQuAwR^ekwqYp(<@HRBS=t-5xMGE@mZ?m`_pSBlh_xDRPboKX; zYH60pah}ox9$=9VaVLgog(c}PFoWaESvJ&!A!N{4kh74iO6B+-Jy)QSE39oKnW(g< zoQQ({Ue{JhieUOLo~4Av)i}ilQKB`@SSgp#zjE4-AT&9g)uKxpk9WxoVRzPyN~)7{ zXdD6;PyTMx${t+Yq}o7P!i2T><^+lC``$3JKartvk^Pn6W2ndXq!@Udr50ohBgih3 z8N$f^`T&uIDaG9cd$x_L)q> zcnygOI~lYp42oQL;vB}b=mcXVI39})q4l4F(86r<&2a4Wg^^Lg>Y#?$`!EqZ<62I5 zz48DCn&)GYETh8Fgq#AkDjP_B6|wq;UYMogNm0awtN@AO1P|g-$jyi;I7C-23$Mm< z5%(2_-pgQ~^IfhNgzBOHpO>>qlme+kfuaes3r0jR3*IVyW-elU3UGOc*HgaByWT>u zF!;-6<=_J{rCDKJGn{U%rzNH%Ap0RAjAm|XaB^%cVid&1X@*l()Ig}FEMP?oWkrG1 zm!Bh)Sq2n1nOGY58+AT?lLh~`P56hfWQ#XHtk(ao0ku|OTlA#tltz#PZJF@tYwe7rl~o2BGw;+o4^*a;AoSpBJ9sAu5xSZqury}9{wRZjr^s<%60?(pQ4e~GGB{MjF$6aeV-g{3R_~Al-$A#Z`ZMGoOp*4w3Ee^vjQV`qM%1xoiQ+Eb>J3RjL_57N$<3jN@ejK~8+M{`}>W|I5 z`8DM~$T;=i2n@X(7Gc*Z_qKr?|3Fv6a>{sMG{VP?aC%>gQ^q|oFPh#HVNmLes69$D zo+7onBOf1^-Uiqd4S+41T#aANTSIbVeC8Y77aUk=CA^7J@{FACFu4|saw-IaC8y6=SA zN|iOaD8Esw${`SJ5=xS#)NsdOI=IzR5UPy8IuL@$LfN>O2Thj0{?tNwG`3fsm?EmB zl~TrX>lnWlepi=9zKPGYer`NY{XPjA-N<9pBvq1-3;#jx%6R}Q`{h@-M=80v7>>bPv;2h)1Y7$G z9Yy)Sm7Rk#8DJ?K=`U)3iN71-8l?n{c^*4i(&WZy;3GZMqAOuiR}zX(B2#u`=;%c<5Bv4f19^ zK|{#c{uEFW&%U-E^@*b}>d~%3(;#G+=SYUIKMTDBIP!Q{FG>RXPFQZ~;~wm-4jvQ1 zA8Ik!*4S?iNmkxz#s(tzo`)Q890pxnK$9=7u)M*&$PesLl3>7(BOTS9Oamg*jdv{%mY^a<72LuT~vdfL;CPXqAHj!;@XD2l_|$Gtj2V zL9IA<=_8z-m7iOFsO~S2uVb6f|J->$SGaaU*5^- zaVO$yY`>L<6jMOB%!BhNP()E3p_C@liz6X5mS1{pu)pGRPbOW1{6bZmN2r0zwsJ;V0n*S9G6oit z42SqiyhD(fb}qtia2h05tuuRk)xiqb{Zaiv^gJv)4hHie?zu~Yjm#G4HSeEWlg}CVw@Aa8q-c7*#=rB9P&Udr0nLf7TR)|nqp;2WJ1~~_5U6T&w63TKCK3M zL*W<^mulTcOr@{{QlA=X3gL*M?fR9lp2^|Ob!n&%(U!-wL+Q3^ogsJ>8Y?<7CAopJ zCMm_OBL=qEJ)%AMqvY9G94*#c?IOvq#Sz5~!B3GMTI!6`GBsQIJdck2x6?9rHE+4f@u4@b4=5OYAR@F4d`_GA1! zI5JQ8VMwfb!UFD^kFgH@8c-2;r$*1OS|4BWDxwuZ^36DkFR(d*<;05 z48WY*n1?-yELJ+#-xe#rVghT9rt`jtukc~l+;J6SK6?RB5{ITl)BC@h z&$`){{JlO$qp8RTA8I3Ty!q^f%*yR_QeH2Mi0(xahMdn{jMS+4Y@RF@EwKOneAWYP zL93t`nxz9S{N;109x(Xqt6>UQE+1B;{Lsb92Rg#S}icAQrO*9kq ze`fbb_kRp^20fGU-71%7-x9O`Oa4WZ^AGID`uWNTU4Ix9i>?d&23@1#eO?19;_l>V zd5qWp*T~_d3}!!O_*S1m13tlL@G4{*{r_1sJkm#eX#3?S_F7ez=Qc@#8FRAEKPo ze<}@S6)b)d@Dai0Awj_i$@LxsgBPIjw@irscqS|6St$`Uoi9q`XJ(GuZ{1u<7AHGTx;ax`TQoGbVjMoD`eB z3xJC7?cFEztwpy1egBO%8}waHivNCl;!R|?(YFiCa)Z9|yM?~%{t}D6PfAAbLw)kX z*84QdmDE1#FF2ufr`l9vrp)IU6Q14pOInuj>Z7+U(ok5WVg|GJ`CH?_{Z-S z`i>tFi@y1+HrkH(Bl6o9)_G_g9M~@weUHEvr&#pe3z-v3f88haZ38s}`TYWIHsm+$ z|5-Z|__~Vfj~8eqY$hPZ2qFQ4gw+&5%Th>bfmaL~5HScykS!of!~$hcC546%O|_JQ zppmuytx}MsvW5a}L6(351%(C#dQy-EP+BU`|Mz!h?tS;YG4dQP-AUN{Z-xu(Ai{pmk@AKZ#|Bv$f>NoTJegD9r`1^b9(H}zp z{t{B`#FhUq{JoZ8ApJm_BY)?J;~$^D(?}lq8#($Ua6SH_>{*f*oS*y*z$S9` z1-&caY}wIpcF_6B^SpnHCHMbwe)40{mWi(ijvtDzGg>2G%L?W8Vra30-h;1>$37`P zuR!AAXO4D8e%^o%{{;NJnB)cgyc}#6`FW|{74UQJQA6?bbX7S>QvWafJnw+OL-F&b zy@%rGeishS&#yv@t?{pfpJ6^%aOmBC#<%;daHbyKlrptJVp}Yv*Oe5~Q6xVJwtfmm zPUtwa>_Ey>%RxDS#c~psrpksfQn`Jj3KnH*3TdR z5BhfFr~}03uZijGS1=$mHCTC;kH; zAB$K^e0*z2KF-iiCF#|odGf`OVF$gF$SeHw(`&Smy!C>ehMoPnjzhfsD&T>lT1 z=@6%My`bZa$_bh++mGeq^bLau)`X7X)%q+0spm3X8>TF&fAib9&K)M*;|$62wh3BZ?>uxMS+vof5UF|{@PLoMt-4h=+7&Y0f4W>Ca#rhDBc5A`Ph6So z->!Dp5E9Rk%-M;3I-M}Yr7f_#uaz%6;9=Vf`-S_U{0h2(iux!(&VXKBaTc-LyjN7| zUJ-v<8pgXmy!?_Iv?TN~&hTEP@GfPuKg+z(GP$f(mb;~?%boUcsVb+sZVUHb$26~_ zhlBsp?>)i;4t)3_an+eKp~u8Cp&w8yk~5*-kMRiQ+pfts9V>FXJ;ix{S{g|Yhu-+f z+-^_kaOgE95L(Rc;VK^pB#Sz^tbQk|3AgYSqvV`VruN_VadKJA!g)5yStJ2}|dnCS*R8)FCI~NG8rZ&4(9oI?VCh>tv#427VTZ`&D86G}H zM5cPB$)209Z`?kxye@{NAEV&7JL(jbeo)D%^fib|A3H=;I?Md1l7^aGXKsaunEPV9 zFI9%P^o?Z2M52~t)H?l@Z@4eQt?(4M)x!h(u~-8AEi6yRCh*E-a+^n3`Qfn1#q1xB z(YcdsBVbcf@_lL>{7z~JO^pRlK+K)6EXODf&FA}4QVQ4;3 zW<$Qi%3}RpN!^~{2)w0{Y^?x!^X8~|$Djd-cF$I2d-Ek{2~~w12aRzRmGEwtml}`O zR2b^m3dL-SV|K3(*(mia!dI~{zuP~=h&bwarjy^d_GfLr*DH0Dl}}k$y)gNqNnGZN z>`dThly`jzp#N5u@#1|9E5ILhNOhH6@RrE*wdYV@SX1S&_Jg#R_nsZ;3V{Uub-tDj zVZ*nsrKa-r!h9u`$0uABVZ9UOiA^kg#^pCWUwlr5=nAsuIxkq^htMtK0QUuiOng)} zac{n3q{9R^p02_AmOcxVTjPoR{$kD=4CD1#ozF=Sk<)bRd# ze%qJd9Hf;KeeCBw9N7M_M`2BVbF_3x$M;+xS2!;U^l=TZ*`F8m@l%R^i2B&5OW`@L zwa~|ea0NB#CU$ytAMc|+F0*cjo$N?aFVUgOWIu3&lGn$L_WTRd#~gp}pIF%bnp7V* z+3$T!?06yThN`GZ1))kBbk(6<;(D#*Gd!6cG{?eMKaxtf7Ud>eSIpMx+1NrDvb&&9 z=sd+&I^hI{g%@@yxjCeE#M50=ipo2{Ri1BB@CICp85Y#wP&5uzO!lg&;r8V31MTRu z7VfbP+M$5Hve(pVlmVm7a|C6^=P80U(@1V51NGA|#jy=1j%`BwgQ-gU64pDav~QJE zX=|a<#tos;w(x+pyh=NwIv120yc`fSIyRDOgJjdcHv+KI9X+nx)h3jxs5|1_6!g2 zL(#)+dR9aC4~4m=50&dz{xED-f)wwnD!Vd1rD^wvry*9*T6t0586pm%R`oq~-O38{ z#A9EE=Y%v-Hd>8e?`??}Ac31fyizJ>(bn*vV=*I_sAHy+lb6y%9*`=g)oI0%MYA%M zr@M=>F6eGVV#q$kyXh$F56N^B$3sju*Pj5lx!TLJ zCXAWpmYq{qJ_@kDx+9t4U87m?@?<2A2#psVVXZmW@Hw}xmolPXg82lDpMLM2~OMc_3*4A zKC8tpZs5<_s->_qcF{Xv@V&NM+Vn57HfS{z)&e_u1_Bv$N|P;PSTUXG(P|r-zQrpI;h2Ty ztBE(71T>2&>%1g;xXf-Lx3YD|Y+W(YOx>q@$NG|WG}~o zsCG<7Al)n=JxQr1Q62?iv+O5#i+aVYWzh7|(ewyRPet&D+~$cWud()!czS8Z_-J;H ziyOt}_D75grU$9_AnY)s!|;rcjc-t68Mq7=M%4llm$M~^jUz0I0$rjeQJAcL!>)UL z$G%%1Weu~9wX2fZ#spMi8T*S7&Z%M!f8{!6lcY8le^BxsX6n6-;h7564Y(%U^K~Ef zw;PT+N~$0?0yoSSKN+-9mr_SHrQf!Ris@hisj_D3Z3o{3@J|6mTk)!pW6|ddTIv;R zcP&M8BIX~s$$zeZVtkBq?N)KntE=6+*@s~^8;`=g#sO(OCo@DzUzL4CKsn65ghx_U zdxNrdJt3PUWaGmmmZ51&Tm@o`q%A>2i#4$o*R)By zlBNZv+iX&7kXKV9)8kiVScuMefbV6?EY89)-esRMxE1(GK`asQu>|X3vv|d zccjqEwQl8a=Tl6aIkC)c)$k8LZ<3a6Cp=|=&F1NZiwKzuzPX|SIw4c#H5 zrkHD8?F%3oNpGF&Pe%uK5>`|}iTY|E#6(b~mdonN3b%4~1}0(e$zFgEgKdVmDxIV@ zJ%LK%uRj%}A!cjJ0=B>4Tf^UzRaWsXwUNi8{urdSN4c^_wb>OP(>O)4s|W1RzpKVE z%MJ*PJ^_tg+J>I3jvdTn8>?J0mw~HFqN)=N9Crl1;gaAzQ0vt!v(cXM2x+Coj*r4l zKn-PKy3WLiYE~H(i5=%C3Kx)QVGcPOTlw-!sz0hbb?$3Z;46=;HlE$L?T1ja#P$2lXN`qJP`)t3Zk#`?Zl6M{3d^}X>Ed*?;5r`V$x z?603c6}Ry4K^WLl-*IYUe6`yT3tUxTfg4GaTHv4ZO}WGZm#LLlV6WXbePH6$ifDnu z&tQR%ay@^W17r1^^4NBWebxdu+swygfe{&o?gCr-lhgvY*eU%QY+LW8^+4+B^2#Hu zN+yt_#ne{b2&PO`6_*h9DGU5%aw}U;%r++`l1yYRaGgrfOFJ;+45ztZTIs4`K3*l| zQ@II;$GZ(q!jEP6qCKJr@8VWn-9ERwXQ9IW`3KDh=cn_*gY)yjLrcyFKgE1-zai#> zpLIl}gBO?+#@+dOL%e@wIHx6`b~`}oUB)heKfBa*fzD&b$)rQ^^+vsBy}#1*cXl}A z!WD{w_i4B+LNwkDw(er}-pmsNbJLod!BAq8=Lbrl*-3PZ9lT&@xs`}U7M z73SFHb&-Ou%>_jwhIf(5u0jvSEyjyUtz1j|y4>!^E(v+>SK;SNM1>O7$o}kC*{{$@ z-@HV%O$=f4Rccv_OM=6aT5EtaLLxN~3&JTsExyMlI{jD&DyFqBgh_Glw#lSeHF45^ zu49AhePK(q>g6C4M>YC3MF)om=;R&t5A1fSBxyqVzKoK#>TWf1ikm(pDKB3FO;Y=_ zc&~K2+V5@&0Iw>w%kSW6Tv zAR8&t#2q_WawTZxG6mtu+giz%%YVmN5xdEh=`V4xG+G;~VnTRNfqHL~r9EJ!ZUhhf zKCM7N5}H&vs%n^h5g)Qr_b7GN8h$qxGgB|fv5+g7hS?~YB_JEkOivHEcOOBvn`sY_ zZP+*hROlr+P8KOQ#rNt8g}Pe#MnY@u6VQ8G=6EJ14!G;h?4y9Yp68K#Fw-P%T>qF& z5^~*o<>u^ho06CBo8X z?(H$xE}V3pC?dP+1(iSMEA)({;J1Gh?WOe1243c1Ju`(|?)s3H;anZfnc_tgkyaX_ zHZ`$2DM)xc8HM6+E+>{)!vm^k>wz;nBN3 z7>NX*K!HCyISsI)Q6>F2&O%0cQXr zs@(Ij3k?O&1XbNUbQ7r5(cUpRm9Y-T_fP`w@h3>V)R0KV%CGHD$nq`JBjHM0fp}=3 z>xt$&izQ6PjkkbCs!rPo{F%A% z5k?{8*DTlbHOq#brb1}X(zVO)m?v|rEr6VZbWykdavd#xKAhTDVPBUnr@!d^px2);lE$d%mwB|aZznAf* z5~V#qa$QF!@ueNS!V*3gM&xcZWYFu@Au@fD#RfceYMZy*L$>*D*=C8r9wTc+Oa3Dx z97Uu$9wnF7e_r9_UJ8^QnW+Zhx|+q+cJ~VM;&sj&tS7>=y7(-|vt#^O zTeS>P#!TNRcJs9ho(?wC<7Kn3(}(2!49WRF2TNUSQ5Vnu={S$`2n+o5RjQhA!hA89 z=V9UYo7LgABHV7H4N*u|CAW$=N(n~0!<;Yi7D!k%@+6H?E#12M?n!BFjD3)6+tp1( z(Z^(mxk%;>bEBPlk4&gMd~~1nT!6rI*6RQPJd5iyqg61;?h?_Ct=IbzyrD+1s=kAl z#Na3a9sg(POXnzj(pAc?jBEKN=gZkW<>~S%hw#IT#eiYJ^;Ra;xw%MmQFL9*I@(_b z_r`&pCs0HFTcX&AIPZSvVbRVSIgXAcbedSAKRXdWp4)B=qidPr)i%P%nXlVcX!_Ad zLWZyXj*gVpT;cai8KtOB5v3+IL5G`cr4F9+YcxZF?nOLtYmE_Ip6k#t9Z8_b5qt#- zb3Ij!fL5in{isF?P7kRgSZvj)Kc#X$G+`248N^2Eg<54-Jme!e4CAC#<5>u#VkPQR zqF4S*Zj5gequ819luR*}VTo4jD(qH$Rh@xRr8N$X&S7?xj1C<>DKIgvgsf&2{sMx- zvVlcqZlrS|ut-khTu*;E^YVeBGcQX;7u_Q}?a@UoD@OT=yz@0}4YqpS1TH{qGD}{+ z+Gc42LH|tUX=`rKK>ez($rMY)WI`fjXc_ey0xs*( zgsF;dRk49%JvBaodMOViS!#Qq(%tZs+71+T6A){%3kfOE0zxh-1pDO+VyN1NL}@~R z)hLc*a&LzHlaz^zQf5HT<(7rvnX3w(DUj(1e+EMk$kYs+aUmvP7Ek6%Of!qM$9K8@ zeQ-Z$lfh-NSIl3m3F+?sn zc;$yzC)u$*VKTP9a-+)!Vzxfd#yP(sdz3!kb9{AmH}$C`C9Mub(t{zTuCv8wCo<;^fN;%H=8Jk7 z%%;v^K?P!2IyQ=2!}}zNRgX|8fz*19y#Wfzb#lO$AGQ0sjFZ~xQ#21}1+n8qJOWET z=Y9=mGHKXS%rY-GDb9Yal6c+ZVT?{ktCz&tvkY_O#!mGT2tkRayvZ(Ob|4iYtK)NG zSQjKLAK54~<=AlVt7$fvp(#7jn^EX(ZT^Jb*1P4qh~cSD!|P!wUCK;y9@f)Ef(Fwp z8U&e6%`LwO)KsL42ydyZ_6LLS_gh-r7d0H<9H*Sg8O|8iwbmXovTVncn#O#v?1Pfw zZX_#oDL&vpj5P45r=6DLdc}JnxHTVbOD5=Wryzh9EUmE!`?eGFk;lthu*8{7L19hpNIyjjGU}QmGny94d? z-=m@SUX-Bp1p2wkOsV3t3K5Bx^Qznp6~6~-4@-~+XYUy*`#Lc@5}0JanC$mec^zrh z3%Rv_2+z4wM}5!mP6%FM-|ts&Ye)*9!wWt1h-b+tPj@LnI>Uyk9!#6y;)#uNGyQ>8urprm;73$}MJirf`{ zeOOgbcmD^Dl+mR2a%=ff-gb?Tq2TP2<5Gno`*u6IMm>@&7NK8xZaq&*G~~Yb_nBpl z*SD4Hq*jHAI(_u+!Q?1F6*t3)xY{R@33D-&B85J#q;PeC^s(-%oCvqd&Wz~iff;&Z z{X`KCF;<)p#Y62ya7(U(IaaU+(k7KozsEL&b_Ig$beB>3C|_}Y&0XBG3eiVl2v9yv za8aqJ>Xc?S@-YN)8a#vaKwTuR#)*r52HRAm0n*Xdi)~DAbTTL+q$$_&`D%0~_45h) zah0|OZ&jJJ6l|7-+24Z%a~+@=B@tcSvF_H`^Rq8{nJXn|^rrA(#qY=6oX zn(9-|=(Bk6-tcTre5R#^oBbKnIIvs_R?DZ{s*1u%$#Y4a1*`$Dj`RbQMGyWp7nJ^I zg(C1*U<(T3=S#&Ic8QG65m#;$92PC%_JOs-ZELtaK^v+7GE=!NqcZs~)Jx(@16TPk znz-723>({!*7R5U8T<(iqDC3>JD3LH-5d-{@GdY=$BY4&8tU7N@UDzaig)iAgm=L_ z5K`YcF)T2MO4%R?3+!nT%&_l{(qg{^jc(=)J{H^fro&oRbGd2o1 zumKKrmOZ4TX8X}Xm9+VIo!g2QnL|N>wcWZVuoj_+;sdO8vFW^Buy(oas@!Dji`jZT z8-9S0JpwdLaee#0RfygT)K7((mZOlULv4M5te~X5E%mOz9L=WRN{>Tp+J#QOz(Xs= zcxWQmsGsGd{}qhzcqV3tuWR;B!RmezQNMS4tFE1l4mP=o5fygV&kFHunMY1!=J1W5 zcn&W#8|U!SX9~BOFq~_#Mz||5PgpG#z2c8cw?l75W4cVJ1O(O`K(0vYRE~V zh`m|r0rxFA{&iAZbYO51?wkop5qJJWh6;5^T!){`x7>d5fSzzFzChtp;m(=rB2=Zi z%H3hDDiYjTVQ;Pa0C(7BzSNd+hXs^CD1U^9co2j?74M+IF^T;46|W~eOkHBCN`FeF z>g>RI()ej%7_Pu>DuA4<( zn~lQB*GAKllDyUn?nMF_WU;n6%5mxaMLG0T4e%VZ?;Bi^aDa^~u1LL-W*b>pQIB$L zSzM8xra8t@kp|yF9wx&ld}k@4R&Q+2I|G2+#m_}eThgm{?w`r%ra3s*#5e43v2Oa; zIiWV?%h$GuyQDV#wkPf=LXWMP6n*>_dSo`nkhgcF)ibs4T)OtNV(mr=vGxd@RHj&> zH`a0&GJ?_`NRw$h8!79h2$f84?2i$Ktw@u38#;q4UCH#u)}$>yRH9I)Yd_MplDV0r zQfe3PL!9~`-qAhnRBvq9+#y+@(g#ty3)Fh+nIT2pW)<2l z$9QA@|D5!9ne7CPdP1uwR*$AVzUC%o>=SXXJPBdp&D#S+>Q!}sSjESDbgSAFQBL&2 zF7;<&;-7-pGe!Ym1?}dNI}2dl-@0!GzovNAo}!@sM5A|p|1WBHYW8a3%tjewgY?Az!3lF2%dCS>Ee~g zhqZR`uLa|R>vMrEI)?6)rWwyZx5F8o^&KO2(9$2eLb5DeSfMKk@FxE`88P*7NQ5o! zJB!@^xW%1%z>+?Y-i9n{nx_*1smaBHn(AUrC?u~*_o^wU=f2Us%xn7V#KUu)8^1^c zA@BgUxc9Sb_5pUyt}Nrgi}pvczaw5!E1p^!jzONM`9=A*6w<>Kb)NROyV`p+Y?>Y3 z%cghjZy!cQv{iJfPC12zHS1K9?FrFxIMn+&hQ3*bccR_dle0ar(9X=b40SFN1A`4JqjEf%}!jP0ti6}M1Z$C%&f>}^70*11iU2j49o9a$ajnW%9sZ~K8jB@>DkL{bxc zJ(WfX));@#A5<7lZyhbIEtu9X^$>o(tZ=wX;C&&-+K#n>>I1<*v!!^<{4VN-I97G{a>1qd)P=T3vZgzO)N{?q+ zn0wHl;mL)+shKFBa;ylP{E$?7v8nLy5xBkBJ+nTCwy2S7te+S_z9*Ef$9?Aa>IF_h zh`QF-;+`NQYjMjlj{Woaef*&K%b5QK2}bYlRK)Y@b#KiBFSDHY6_)S(2oR17$({o8 z)p!cX-5eyIo&xezSNVofKR%%I1O^Bx=RARp`GmUEPGWMw4e0$syUXj;?yf~5191Uw33_j|Vf-Y4#)`hrcccwJhM^`EkG?}a~ux}iZ57mIk zMmy3qH*3{kqE%MTFbmUA{6u`c5^POnnv048DG<3}eNHMW%C{xbW4+fuK&zdmh>Yds zOgbBRVP~SR_^iSHI-C?$*yrttP$@Wn>a}irSpZM;%Oa=$y5Rg7t=wd*peV@7@xF|o zxQ(LU2ZN%_&WXdx9*fseYdc@cE#qJRSyr*slhaGsedF>o?SIExd0S#zO zCIPNHL1jdEeCoO|3)qR|3gD;;O%4uUfqa54)hM)MZCu_=HjDEi=S4WqBpj{YczA^$ zfS=ZKqq;_)Zh*?*CbJ`W=EZn{^pS|~aT$gD_+{uzR3JHIyHehr=MZZ*1fD1 z;AeBkJmT=H^N@AxQ2iBachn8xM*1R|A%#PALZm5QxzHX^87+I{hL4gjJL9_~h6}a@ zs$j8hH1yQmNL~NU67C@B=}b}sMSmh&gT>Z@-Nl|1m&M(?hn%%^D>J1E1E>&no?YNo zZOokG`OqWw0Xmn! zm2D*~DaaNqe{>){_S=GrdX!@kcB3GNo_hbPjVmEdhd-k44-LzavJ&swy3gD3nQ-ykC>&SwWY*9WLNz}Fkhv|*|_=Z5&Nq%Z# zN>`uW-60K?q3b=2Hxvw_%Em;Jl&g>=JaU$tngVn-!Dkja@=>GtP#jt(CkRGz89dT(o|5qP3>S~q3xBXhS6bz?3VTe%DV%w5jT9YL_)p7flf)nBAmsDVW zm)UpLcigUW0&ahOg96;naT!9#!;}l5simSQG)P#D--T#X+g!nWBoUtrB9V02gx|S%InZqWIpy)j?odqrlVwcT&AnBrf1a^{@Widbatl z-jo9Mf>!%_MBSp_sK%@m9g1k?I(Gf3AGqkM!)>(MlT(f}dL*kQvAFHA~ zm?Nyw5RjtwL>B0`E!VIsWKTkzs#HVlD-g*T(h_glX3UDMr>LA@;c-D+o{$7_9m#@`ta)p#S`twGtn18{8lwPW-B{FGroC@n7V&b~tUZ)0T8U9MI3b!A5b^ii8H9v3h2 zD!3E2bcw(?W3b~X#X-MotG5`e4ZPML-?u9xIM@C{XrhX3<$1b*;G4^y$h8wt3ypeZ zpLfj}nv#ZPi9h<2yof{gRWVM>VhY`l!N;o6PMM+6qrlG??Cirbl&v0}WOpNC(oy1- zpZKM|fne34YSKF10o#OqT8SMz(j(s6aNxH*K&Ly8{JCP>y7sa_kh*m$S^yHQ71*?jH)N^ zOr9!CQg&KX0UF=UPbK!l)O-%br1^;8XI65X@*{O$a6ZLz(&Emc&8{k#RLoK8wRr|x z-9iheyJ%#YEES$~Ldx*TjIq)TA14+-y(&mLogqd^B=g=aA z`3G^GXEod=LZ686A}>3*o;A3pQlPlxgY3?0mEmKIfwxcwrMqO#b#W_zMxofOO0%PJ zu^3ZwG)A}Xb{S$y*EOEHh8%C_{%rMiIG&&X>r8`#pr?aA;j}B34^io*iMSt9ha}=X z!Wxo>dJjaklW={Ypf_SKj1p1Upfc|od@6n=un#ZJzkVTtH=m(Tsf3T^to2KtV52HY z1%}kcJG6oyv7=iZUH7WRD2s@W1&8M|s;bN~}f=3ibOf$+J3)8s<1@SNlXpGN&4CTsP@DCi+w{3_KR3?B3}x z4$0~BEctC5(|sPM%+gDC@f9K2ib2Vovm{f4?ID14k>C!_5#)Ttoe1u^SY?Z5`csiPxPC9!OIGXk1=4^DEOr9_k zGk^uwz8}J%$pdR>wmyTJBmQ38U7pquU?I)jM@dRh>r=6y`H~YU!XoFmuv2j2W)xX! zM-R1(XOiP8?YeHrq6YC-B}pNurV3q!U1Lpt@5`uLQUl)_pLoOpog04$JtQw$M%^PW zqh6uY95c(P;{`k!|Cx1E#?O%JsB)tBQnofAidK<@A`6CkcG=4xzyr+4$~tN ztoC|{2(!p;a#_n&p{-SJGkmtlK#=g+?S~5aeH4*!bY@-cEXslW8bsG5E0+}j<7;nA z-sRYT8S)0g1o?*+q4JZr-FmzA$x^5j)1x3FM{ue0){lCO{^}2| zR55J!2c@d@cCH)&D0Gkf&uvN97ymF#jN-e$u^wMvyqkoqFJ8eQ?7x}cCDs>xUNzrZyomQx zT;c1Bo5^#pPYje;Up)M4(jEP|2VNeYTVwU>i@i{gtS>&qZ@ciPgDgVv{NFzzG5Jk% zyc5nVM-^cb3e3SGUZ>_jdau|V^uSAPHms8hm5dBF2cvBO-6SxzkoV%FGTGgTNY)(G z*|ry84%*o>ikEJIom;1o?baY?AE5#2JH|Y@ORxs6i_vJqG_)Au$@e34F9%VXp1ZC- zA0z&sgKMZq-;Yo>`?-`2!zyUYu<=9Mv)1c!k7uvu>wfQboalAv`w@gFC!dh;o?Th6 za-4iWVzSn6lb-$L-;W3wMs@*`M|+D`*(p&)V$ok0wF6>trQ@5+9P`9;)AU?sVwPG% zx+c#bZNl6a+#iBD5aUtb4Wy=fPvKY-)CNiI4D}j1v(e3thw-3S8>BXyltbltmo(Z{ zIQaM!OuEi>a*TZ;Liv#DlJU~RiI>Lc5?VxLQr`$n)S4kY`CU;QgTpa+?spuOUm6Vb zX-24~=`ZwaO|x-LFgRfNQ|cMD(d<%49TS9uBF_{Zaf7 zg!T_ZTu&;EK^I?iralZ&)e)Wz6Inb{*v9()3_O7b$mr3tlw|TlQX8w$qc5O@iu9;^ z80`4V0&l8Bf~rfu3vo8;5RJO8+6VKh`kQ5Qw}95+1^ZfZ?fALoMeQu^e}*CjWLS+& zwjIS!Lp_;sQIKL;{?(mmz+Z%3IeFh2;SsDIto_BtaayQR}^XG82mk=@gO?!b>&vQnIKdayU zWDrei60lz@?&qZ58zg3hCwMiJFxBzLm5J;_2Q(mR)IY$`2EQVu((zT^X{hDk<&BWP z!q&e+-B9G%eeUOan0M?>vVAAXO^S#)%1C$E{A712dnOWbh5ge^X+WckcMAGmqgUFh zOV?zRO`TGQCs&89s5N|wiBhb0I=P{>H)iYcY|Ju4b_a_Z(jXDvWTW>2)?&H+eR6)2 z%}vsFvO&=+w!}v|pu1o}R4lO_>b(V;B5A6mUhU$lzo021?%D;~hS+M;O*)Ek9pU*Q zU-{#1n<}e6O4>AktkpmB5f=x2Szgh`(qR{+&dj(g4ZC312Rf}R2lWGxA?3<)-o)yr zfKb!jHVTtb*DDc|ap$Dn0$1WHrc_Quq@4+N#4BhQOlIWJ?TvQ*Obu}PzT4l157KQ_ z0dY!qg)i7>IHRrVD(%qkk&m9j+~k}7zJ^}@Sk2Q)1yPnb(d*g~&>lB>dr!O`n~N!U zJKb~x1#0Ll^?_(~auyt^PRJh?`z%Fa&i2zD{!e~d?B`NK*uc*!7ZTVSnLj^c*&-#< z197uW>(4YgCI-vEh+bto{X|dL=_sF@Z?+k&o{~o7>a#!0KRwv^~+O^25V4G(=VCPs2mim_dba)h*s+v&97(9ATD5OOpXHSD7i* znXI8t)pm?m#gbuuqSlA>6o+i|kk|G{>Esvvki4X%29@+VEtMdVZo{KDd21P-AY>@(HKtl-%$Vkrz;Va^h1UOw#(5i&g%Z zSO1}E=i7K-s0L;wJv`Wb`z%k>X;yUK%4zro<e-#{xWsc9{eVUez%>h zl0@wuBTrP-g~7X3D)jwJR46niJdG|=deS8uiLQ_wZs<-<_qJtc);;1sX{wawKzQ%d z^BBS#xh|<3G(yHY$5E@UbdyaVo1Nzd$i`uFrAqe$1)|brY#Rgm8&@e+@x}Olf8P5j zTP#&ln?Z?ekiNhTcL)OzBGjhkl?+e8wl2}cGwlm`-Ji8p3qxd3;vdyCJ|D)@Vw70m zKb^5W#fh3cRQXCdTP%i{Pp9kV6@@sfMbr=z`%Hm9_3cN)ZBuxA#kv7!HFH}AZSwPp z?P7l|*VLbSCcPz9)eMJYj3vN}A zAl=VAOg9>BiEDkn%wd^b<682z;H{)nl*yW1F!FG#$(}t`aI1JfbJZvp6_wO%*SX%< zeB#ytY6X5KfxEuKEo=>=oR_SCiHUZva2!zUIOPVbbn`He&;lLE<&v?wTH1j z6=vEb3W*BG_T)`eE8dp#eAu&NsQ_w;9rnOJPQIX4E5%51CUK3*Of$77VZ;z1OO;Z9 zcYCX@%mpW<`9uxCO|u%q0^<%?D#Z=R(OAD zV3#^TcEgd?DbV7CM|S}ztGw-g98Ui|ygJ?8RdBv&KeNYO1aElK2OWW=nUEAA*5=H$ z3L(~?+Vq48a|*E<)m1<&b(KS`I&~`$tHQqV$HbH3BOu}|l`n`rTIqmTrUDpQy0;&A z`Qc#-u~dcrluFgulqrH0`MrL6QK1+2Ow^#@Q$B?Tq+6MRSk7(IibNacRrwZ_B>eV# zM5q|~%p$M7ilQmNF-b{Y>m9i7HEN@|nxh=^eq5A8Pg6Qy9RkbATH3kA6*;$(<{emA zQIB#QR$P&urWKv5DMti&$*?JmhFz#aCY}=@*2#X}cG{9&y_+TF7NCx|)+S2?hSW(r z?iFfNzI<(qL{Q|7e%q(0tpH-RW>WO=+aXqMQN1mt>%DzUsMp9F>s5U~5S1oo=#BmU zd?gEqj%JeadO)vlj=WmIP3AQjx_P2f^u}^$Q%c96b6g}RN+aEBubw-k9EyK~dFov(V!SmLa^4svXL3XqePxeLE5UhmfScLxE3=-AhlbpF^4a zY*I<>cWxRB!OOOj)Sc1P0cf;*Fw&pVUg%8$%^AA@W@O*z@-(}2fPc!@4aTg<z@b28H^r=@Yks91V7Zdw@m(;+QI&sStLvW#Jy6xanFs*9_ppX`y<5Q5K$ob zfQPfvtT1rG}PhG~ng6Q!WzQ$LzIv zKn+#UB=hgxOoGaS-L7HvGC1TvFM`?a`X82O!5Nj5X(Omd_@Y7F!Ly%>6-keY5}d5* zX4g=`T3*3lCw?ziYLhE|c;DpuzKGAP&bGq0LF=CubP zX+3ARkhv~qjvA+xn(S5Ks7xW%r3(20h18=jTc9<{!(z}4AAE&F-k*D5=WB4k_17mv>M3b9*d zRP44@91iY_gCAh!nk9&qP@k_~SIN^IzPu;HI+I+sDIU43$L!4LHUd%LF{A3-kdC7X zItqU1FIdk+1tgv$kz3{#)4fN&T`Ix}=THb&ap2yiez;l?F6%+iQw>S)+Eb-j>803n zGnM0cV#MNZ9?O{QsZXc2v}q2x?Yme4S&OQ|a;8Q)<|@s51h(ZD^`qs|8&TS$ z6#qQa?Mal#)O93cv-V^_X=~t^Y9u%$Z%=GZZG__~!u6!Orpl_oKYCK-o#^JmMNxpS zE^9c5C7S41sc@U?zzv&JJ6FTU5x)PAJv z&U^fbO4^I|wZ1|^R!O!%!Sp}N@BH_yWn*fopy>PAFUV&yaJq^r+TXedYc#7{fBtKw zwByHGe;$C6Wc~S7e%mUPND_D+uVH;+Li>~Y-Y8!Lh@Ti~_8}EMd4`}cwJlhGZsc`p z>n7t)2)6F}AfKHRwNB?jd5i<1$~2urTge*PxfiIDieixVa|sDEWc%Y$(ZDG%4Fk8% zR;i9sQKPNWN6AVlc*&c`^{ujbXmi1S)uQqDiq=zWxRC+uBc1CjWe9VDoo^43HdU^v zJkq8v39-?@iW8*-g&VNmXzZTcI| zP?Dx+-PS(zhHA5`_%bhl@1ISuw_LX@vm4Kmj#*|;6eUmZL-PA$^0P>;L4xh_(*mf> z7aH+x+2QdWLCWsxqS_cO#$vgYhqZet9{ksx+>3sY3GgKIWh0q&tVmgXXLFf-MZ()L zMtpGp-$Y$e_O0i!uT_n0DsQ{c*{FS=5Jz!~ZAT?LhS_CU$F(u7%0{HrGTYfpSfH#G zwyU0F3rL4N}4`8ej0uQbkVmh$A3B zT9RsN;sffw!YzVPg*HrE<={+}>pR-wD$lNjZ6Zp8ETgin5J{6C(#R}USqa`f#}_|a zMf|9q{vWZ7)aCxUylW2hu4u;|QsAB29u%B3hTHyo!|j~#_I+({hq2Q8`h;VB`>`6= zZPl&Z$Tio+)&7bc*p0YlgF?x#ccM&2J*`bG|4cTuOoDWM#8{&fG1A9zYvHxVaUVrt zXbY5#S~{x7wmVxQLO2v`TV;Qfl%PvQV~m~fyjoIGS@3N%S7~U99UxU0?f<+pnfWV@ zp&B$%Js?O@N#ZFzXAIYTL2Yw02(WD>r({pT$w{P_B<3DbgZgq~h^-MFc8QBoR9`E< zo8oi!xi7E-HkU#tvenhW1ZpR8AgGoGdlAtpRxKw!Bh~UPY1KVO&gLeK=8NBJ@}a4? zzWc9FRZEjytD=ymCMSDd=Dy>+>I%MkJI%#-nN4s}TxLHk%8cMF7 z2oX+|OqIQNMyT7BOqCOGo3lp4%@IYlDic2=3eml`kr4ASvAQ``G6@zCuP7{0HuFzW z`D4BVGo);uJB`c^6wo-6&^VKL%#bT@+Yy=|78c^X-6BbuH?Lks5>9ez3178Bc(F z(EOGLY9Gx}q`v7&SM<4zc&}{jh4OuQ&jqCSkQoqbP?2ZC8L3FAh>VI{B>u3z!PB`A zkVSR*^^N{J(2{_>HWHA$bHvuDz3`bgx>L3x`ChHMmiJH3UGCqj?bR)P(-R)Q@HXmE zBZV_r^ny)Tehp5&^n2=hLe;IZK!;9w#!wr+!2!7tKXJZOk>J-ttTU954GXbtDJ_73 z!p&p3&ISl(@HAE0^1LF2cB@kxHLOOY~*i zWuPj!Ixg*onqaAR|DAB@@ah0dweTuHqK)UO#2phfRnKInavSAJJCK-yD>YnyqC*_9ufranYG#5HS|G)Kd}Q#Rtf@sp z)LWc`&myzkD0>rBo~M2N2i&7v3Rk7-RLu5rk9CHxt7c}rm<0!CVaz454h}7=?6Ux~ zWY3R`mb+K(iJcmE*?kKw=<`{hVACU#;~e^5=8mu!NlJ}vPM}PIV_ALZ&g8#4+;y$( zP55HzCNysoEK4_eLlfi>a%q4vKN`lpEMd!|pA*9e8#p&bOz!baM}QW7y}KMQ`NLrp zAgy>E953I&@j?>W)TZElkt3#=FC!Fv@S@a7G5cl~b&~ap=zt+6GdN&a%<0%j_81S5 z3fCS2VfqgEl~YAB9YnwCFu72m$}nE zlb;gxTGgyjvMY}pBz<}}Oyd*Pk#I&D!ybnVsx!Fi2{BIAqnFvWOoOBxAC*?>zy}(t zlpke(=5cg`MvVHVOzOtFgW`i&^{k;PyF?aQ(RfkC9XYyCh8|-GBR~5GAu8p#D3tLr=ZzlN1B3!9A#?kz(@lESUXuh<5$MO?kt~&sS|w=n z%!Af&^iGu2DNXA!6jFHXLei3C603U`jN528>M6&tuF1Eb^A|b}3u5tymav8+S-`yp zjC5#~hyymo7l7A~KD9m(!eWfhWtjRs30<_#ae-!{KSK)wEi#BKp35LOGM5svh%Do0 zo1BR^De{-4@g-&XNvvkDU81LR#EE|hQ&t+I+ih+Rx2@s!YZhj(Q@IT@*x3=%gp~`S ztR~*sCdV>~efdbdcUQ&uk}`|6@5KAWDen@{2GOiZPB`0{r3f0_B-|GEHp6eV$~J2j^YiG+Q(j!3&N&gU1@`QxT~Z_s z8pC#|tBD=ASu4P+UauvF5hZcEwMyNiH|?`MB-dIFrBo0>rE+88G&}KGbxgFvulZmS z99{u~*;hkOpQtMlz*>4mfA%O1$ik~xJR}Vzmxfo}%DAO#{EsxmDg;Bj$t4B%2%RTP zB>|&gCKPk{m7*$9Tm|Kff&j4I|Fr;CaN#5%%Qfd5Q9%>@irORAwaw2>NLGXH|4x7; zcdn6vBr@}x2!RDh8{7f zhYV_1PO`_maj|=Es8)a8Fp=xI21xQjJS>sHe&IC(EJG~`lYO@UdHnx?Yj zL3R#2jC+s!xAh46$}Wdpm`7z=4s%q72ep)GEwv_g+s6Q8NVp?1+$Dl#DIyzyIJ*cY ztp_9TW4X6SJ=g! zxCK_XOgXZkOb_hqjVgC~2+u1pycK$VaucsRh6ZJoO0b^i4>r9}`1D%jS1H->BLew7 zY7^Y3@t44Nb}Y%~23?VSy|LTT6hp{rt#UC!6JB;J?K<#HBxW$2r^6%i(m|pgkGc() z@*`11aRPMUxBBXaur4O07@?f&t#JHEw}`zcU^$mcayeen3!Ao6q2zU;Ho$UY!`@56Lx!z|HogUaSLJuWdjof5p1DVhhtI!~zuAx93eMI{FQLjaFvy#mMl8K(Cxa8_|IB@F9UQTM`}Az|P2 zZz7PAm*`>Td$Gp{i7Lc@`DIq=)UpHyj&SD_4t3ETVc_ILB)9osWX0*6;?IiKE<8?W zb?X+d0=nWpFQy`Kz0?k(tp1h)JccaTrr%v-7>Ra3gaK&D`X%^mLi-abM5_UCCJ>E* zNlOaQStYMibaq%7ZHf-CMH(^a@L{x5-u7l0t6n{n?>h0x&Cj(DueMt-3vzyyi6`(u zdF(vL!3jLm$X&WmAvW86gj56F%Lul6k5U;s{^WgtDmA$nf>-KPWCG0UBq-s@Y>#ZQ z=YV^1m|m#dWNY>0vm<&8ZaA`Qfu)aE!T(Er4$xXe=H-+SzM;I#9*L#Y@&Y0AB`RNr ztF%)O7kS~ZEFlU$uNopD1**D9?$HeNgd3F_# z6l<-nQW`CtuCg{g#XGmBYm?=6?BI+?XC8%2-;9~oCX?7A!JrWN=3H>lbXB(W2)*Q& zkJ+$&Ldzh-Xg~fSUW%@0=KQ{cg(Z;4(_(5DMY0;w`)>J$C@Arc{~a>ujr~qF7qM-( zyT!IYjBvJ{$4vp-y2+-3UH?<9u6FUIDckhkj`s%Pw&cdin`g&}F_CS095`^Gp2D^f zG*Z_c2WQ-lN1nL8JuqFlZR42nu_#ePu&&l__LnT!48^)v#Mcb#bk890uglchM1QM8 z6Y4ug=iq1=a}Kdwe_F8Cc=_@eA9_N406he@zO!Y(4s&?t`qumC%I~NHHzssZQMf)- z<09_j&p1McK%P2wL>JRre#GW1DsuR^PEmOiN|yIi%ByEN-bWs@aK?tiZXd8r3N=(} ztzY>&(H0Zh&8-Nr>Ivp_HBx1FOppZ$Ud)(zxh9!&p-HkFRO=ZdE2pQQ0?f^UAi6$i|fgC62-nhcCMIz zQlfmcs?lVE&sE@XG9Z4l()UCn)VoRH|~k@DNpK# zYDSWH?DGA0SheaxlKl1kRjdAcFVqjcDGj(u_|p5jj>_efa`$D5TY`IRAH*ndODt@( zoepxAeHPlpvc12xI+i8ppcb?2bIOJJZ`^lO54@1>kbK!R*A>1TwuvW6w?gfIrx^2; zI#pm`kC8LrRMA!_#?Uf*%ezz?MWS>?Q;?tIHb^`qfA-^ViwyKu6fZWe92d$oU)BD15zEYIn5A z{kt?zSi)VQRFFB0I;7hd@hWk1qD0-VCG!0Uan}tAtl7y`d^DI5*8I=cDGiNYCz^Ps z{Zupj84f{2CF9-Vsn#0>HfUyncPm+6^f=^e^ZqU3GQNzort6Q~A4+Iedar2JTHkNA z=SMv5y3gZgJ4dv3Yf*Ff-*8o@hEDTKWSLcY4R29}k^YvUYGpvDDQO+AbA>JhTjJv zzY0W2n{qqi@;r_GxG;fwbr6`~y#8%~c>P=Pj~#VD!A^Qblw|N-3hoyAfDMPEAm;8g zEn4B6OOh1eP0hI9x}Zs#u}QSieqDq6P0>6}VofuT>@Tjg*)*y#UnE!UN@X{At}xk|t0oo;m^Sufdt&4133QhYLqIyT*&`q9L};*x>29=^v8J)YH{PcKiBa6dtPg zv>o4+Qjse!EQ6|i35`9y9>%*fuEY5Xsa!!SgB7^o5Af+0Y$v8!*&KS^{q>+r;pCE_ zNm1bnp?8*%s#{e2i&|y#`S3%Z>ZN2o_il2VUt#lTM85V^~$B2UxO@Xysc$1@%CebLqIEVs9u_?3?S*U7dd1t=7Zc-ge4i z#?Wk~H1(^Xk&VL_sBvGFJrbcdI`h)dcAtXt-TB5L-75sSlsFvW8F%M#9)S)>P*f9e zK>#IuX3AYGNf_M_YiGS8@K&14P>fQH;OeQ!cQJ1}K;)+#xJWs=>WgydX`16!1UZcp z2z<$J^zT#t7KaT`ny=GTf-nnO+sA|+*d-d`i1>is`cMjK;x?#R>=+O-A%pip2WxCe zf)&cYzvoZ3ujl(IY;hOG(sovt6U9UyWmEKcoo(TesgK5qkR(Dho2-HYRFJN=Tn&TX zN`IIp`t(NGUp-qkjl(nRIur3ccI`4Nm#Iei`khaefh#B&>5ahXSKk>v*a^KOneVhD0$0 zdhbs*;E7CvR6QvrgE=oJE?I7X#dPd(zsk{c{BWQwAWc{#q{J;@ukXt!?-+B|?=;xw z!(Uy4-;JpUdL;0pfgj~<&#MB!qV6>HP%*UEo>DGk10b(8R(siN@}JLofo`^H?D>ed zNe7b(g8at!Dw3_n?nsJ~nQ_Je8G5`v#i9wva^v4AS3l{E?E`qpG|V;%ZTju((55f0 zwoPYv*@N5k;eKljW4^SA*P&I(4Mww3V`7pxAr9EB`Q|PmmpHPFt5$#kNsDbB=-5wX z6_4_uxGt>t;(|y(ahQ*UMYG25CJ^}N^sj(WpC|*#?C{Ta(wpsfrnep%f$`60oy zxuni6`iAeN|7a+GUm72GIe77YhrP((EDA{C@w!xX=ZJl#35zDdVPVaO#|Ln>z#rG7 zPM0^Iip);^tSw|c9w$sISF3?72|g#|*3=hZcC0Un$BXrqUmBqI992(mcXK5k@7BG= zkl{m~KcKTo@hE22j19ta9jnExi%1eNYlue{l2fI^t0G)-r>o=DB_S9XdZzNosxe?b z4_7+6N%?Hq1U<|k?+4tGD7)2cqg*HKxaMmCuR7plJP&g`7Gv=0#DMT* zwtpnczCPNEuy+xE;Z-3%se@Y2HaiBWm0|c>@#q4K#|^$XMHuGtiAs} z7_a^UR28fDw0|A1u^%=XB!#%tZ?#4!WTV1DWV&peQf&|)^~P526%8SbE&$jr6Qq(6w&@TT z0k(+0M zv5@J7g)CGd-9?4;?Nfr1ZDJu?6c!@GW``FQ@{3Z1yz&tPy%fN{(uFE4)H}c)UR3P( zQpI*q?0*8-eIoRg4e8fnfPF5$ljO`Xg)HX&TY&w~((B~_SmBx#5Rt*wVOcZAFq#NF zh$j1nBo|HOTTfjqrHj9~x5!Tc_JDGHd2CS*Jxy~QguD*|*gY|YsIl@rCF%tLdzchj z7GUdveMveA5iir*Jp5%a=!xymRI(Jnu26#43HuW+t}M(?iFz%H^~M%M&me%kC{kRd z6xYOp`ut&b4A;bhbhR037^T6y^u4y@HH7E(jrH1tTt9wHz=8gkCN$) zJwjZpO3MvOCX$s*Z|s$?g=7tbl6A+@^v3R1vJ4JSd%RKkumXux3@G&mP$15O!xJ^+ z>42}V5Evqy9dLLPsrBcm`x5MWAr7CylZeBWQu5lY`XFBSHiae>;_yFXZX#R_DTTwo zx4Xz0v+UQ>PV?=wJ$0S#>x$s;NuohTw(ukRpFy9x6ZB9q@)l1}t`5iH?B85whfz1X zxAgNHUSP!G2k|_ngGoc;@ST#PQpN#=`nK4Fe=j6sg1_hR z8@xPkO2FT}x@C2>3f& zANYG`H5%rwOKE95fB<<7O)Xzfzsmeq-057rKe6C@TQ#Wv`i`*QnZ=hPZ*nDXQ*ZLp z3UPt`GN=rv5Eh8N0>2VKKM5BRS6m3guuGl57*bLJ`GAXApR^yM{WWe(nc~dK_C$K~ zkUeC#o3;w5-9>MKt$0ij#aMvg3s318{E;Af;wU;)$cVNvXq=*&ZNhKik1e zvE7v$+FO;aogjjky~XcC5CJ~}l*0bDs3g6#21-bR2-+ycXQ(W}F1N2nf#;Yz6dq}V zd$>-7I@YNE^`L^rVbSFk?MJlEK&D&tC5=bsHbeT$FFJAq9K5;m z9eR|+k%!YtWqqp5!zo&4#(MS04p)&yWG}f=WKW|k$lk7)Y>U}-wLPQ4G86vlpG za>eg;y5PMnRTX6LOkNeP-RnHz^0u95S8lgO5};xA<@>G>*_~|O2iN2j?*XRQly5keFUN&_ zUv?ugS1O=PO(LtVHa2F}*UuVi(~P-}O+7DzqJm`l6E%d%9fs%cC0n;n&3@^Ay;E6A zWdCv{$|@Z<8vi4?&ws%z*Z!a#1Gf%r9|-5gk9zNqA^Xz!k+ZA4D^*-2FXn@D=~?7k zEM`zt+@<7b)*K4BQOv61z4xZCI9%=bcE2kj4(`%Hn(8*|tW^PHzC>?`tY1nLP)kF1 z{32G4hv;~1^s|XBM4Dj5?Yp3FNRUEu#e}?W=~C{ZalF^F%ueO1P}PRm%jzUizsPy# zOOBK5{p~{MR6(#fpsQWKo%(g6Vl;`$DCnNyxSlt4%%`9Xpp^SAkEqU-Z-QPg-!?GE zZk3RCWSP zxLgBc13n)`P|I60xS3L}8`N?giI(?fJpaB_CVs09ui}$H>z~M)sIvG_wyM@PcOz3z11T zgfFCsA*17XLSM1JtQ14MEQToV|JeIyyM$*Mvi}awkUmHgLmGEl9YcN=_Z7FQ`i%M5 znOsqMgb(!vKoj(cs~uWpe{twS!Ub%b+N6ndpdDK-%)y4xsV_)mJC}TOpPAVA$}x!feUzs`C@duK7jC14T>vXfkAwE85z0J#I|yt9sP|PxF=PM+ zX?HOUG>G9j>ajAXBg^n-w{wP{^|Qe6CE|Zc34DIU&=U9nxk^jmb%Kq-de8OlO(n3u zOA>gu9Act2MUef|$Pp3b<;ub^Y=Hhm^1?cl7WVYiPTw{R_(|ctn*k}#y5OE_3o-N` zRBJpZ=NdWBfdcy8pos zHNDh)szC5H=#{2fY0AAaKt)agHa_St+cHQ^bCfDcAiAuPh56VKJ{TS$8B- zy2nN-85W1_89pfKIE*FbQO+DCXxUos%914ZDb2|UIsuWnj+14%F!#QuL^Jzs>=%@M zm7NIc3gL$J1UKp9m`&t#jnlrE?_=&)Y5OTj`32(ukyktTEO=X!eJjmV@OE9^%9P=j z0RAoYVjMjxRVe-hPeK@@n2S%S1wYZ>w^Fv~1>ji|&z|&WU`*sY<0raQ*$89g>0D~h z`ia7N?S{+J{TYg;ne(URtJ2wuL%H(1LInKY{g=HH;`@|tYBrstdZp8kz56$NH>8W| zrT;i2UU7l@j#}0KtiNj`o!l<{Pw^`CS8+dyD$;w2XDjJEWYSiobSC9~QxQ0no5WF$ zJ}Wqrp|Ff>l4H3$zqBkrRxj@pIU@TLzYW=Ycx;bs z)|R!pB~N7ev&td z9d)ru10?Au8YIS9PSlio#15hm;T=78?pmrYjsf0$iQ>nGcu(|jYkV1L`n1Z{H`vr* zeh_1msYT3B2(p7W!{b7#VYcu~Xtw`DX|I zvBRp^_p_C5H|L+nP*oX6UUI&epLa8%{qeXZd#0$0%j=AHI_+5HtBIVN%InmD_q+g# zJMd;#cauO~y5MM-?)R>~kp_J|4~jJ{wTE3(6`jr7XRp`FG`q4?&Gsck#<7vYv*HVprX4&D@YIg`d+KTnDr`Ja zDiv%HVT5|CHodv|Oi-C}R!2iF(7) zG9VoZD*BR{I_4H~d27e?qtl0`L9e_=)wL!SufaVM4Y@kM7e-}6qm>>W>INaEAc zoSm~G%5g-4D%2b4rb9p(W{&2Ggu@G+g*8m!VM0Ut_9Uu|UQ#`4g+AMa5rPScWMJR? zY1gJQC@A@`|9?XJ!}d610od{p6L!Itm!!afEt(tQxY5PQ=;B)5)`S8VY|)*Nw^&3Y zCS3H73cJl!KMPGvTUl$m3%>HsP3|L_MTDQf_wvVA!_{;b#tRnAy{zjzxORJ?@hGz)6Z5X-aaAHt6p|yEJNAL>%WXRO_Xv*6* z4`e=jNhGtrd|wlNuWeaK-x*stUvDIx_)`%6P^8%^*u&h3WL4t6P`{ZeYVfH40N7EI`MSp)zUfL z74{%mwe?kKn2w50vA1SI=eGy!TX{mu1`+yIC$u*Z+7EXN2sJu8OX#G{ouM0&PK35g z7D_U7`x1m6;}YcO2#HY9oD-q(gOD3*z7hC(n3uoIj`kce8gCc$YD9!u$wa3T&c_a$ z6xr#ZYWs<_FX~42D+Ad6tcFg0NJeiiKeK?%_nU0R4wD2wprfluGOYjG9{`)W_8s+) z{x|rQ*k|2e@?qiq7Sh>?{l<#cl07-UIKfc^rvw}|z&ok6UR^s!tZ{6FG`dA28&~Z| zxnF^KyuX31Fb)zF@!rh~fq)*a!dP`GXbv}aF`^XKBSjx^VtINxN1Q>`pcq+(e8mS* zu>%zm)PFYUC*-HjeMz70XNh#a_ydt24mIc}kU3laNYbZxOAScYBV*o!PgUL|S{X>A z8aIo0E5&kF%mZ^3YETLo}lnCzxio(hbW55@K;$}Yl^D^pg$?frK2_OsK zY5}te<+GWt_wu->&OY@i;U0Ea`4S+w=NLU zC`R`9`0{7t)bYbF1yh|SBbAGhYOwnmp}|x;%?);{R2nCAGdyu&dnoZ68}UUJX?eU( zl5o|fTi2WsWx;4K@29J+?V!Ti8f{p$2p;9t1X1iALV!nNMQ(Z$WI~XLO0Z5ob zhVr)cqxVmD5}Z)XV%tTzh^3T@^!2O|)YH#woUet<*ji=kk!|k@Q)ko>7sv#is=+S& zwa3WoE+lE*9Xg@?8LeoIAXkQPyPS~(&8I3cG~f6+T}#nyDjoY?OmnMpVSp+jIKOk^ zqW%hdR>LCDd#)!+>79MP=zT(nCeZr^4H!)Cx0MUTU02UEqd6b?`a5Joa2uzYq)iL3 z^iY1c2?Ey&XM+fUTJ5o=fNn;{RGtch#vs(=Hk1cV7!Eq)Ay5bDS^MJ5LYKg76k)Qb zu(?2}e*1ZGc4efe^RgJw5VvL=_z!t`Qh{Nk^?-eD6WY)*W+j{u@3)iql;r*2WEY0u z{csn$^{cb2x6`CT0^7s;ld`se!R~393)<9xkBz z)qK~S=x(U2CPm%Iq`E{`JEo#QHMykS{2AKUvFl9u zLStyD-K$P1*$X#|h#`dS3h&AyVcSSnE(lwApSEb>*=o_XQoRUB{ci6JZp|59Vi=o@i!(FJ6txjDNda|n~mg@emmTaGj0pont_ae6SaKsf~Rwp@9-nX9x?XFvELhe)Pv<)j2BuM^>qIW`F+Y0 z7Ch`OwO=W~7XMjtzjySfCHFTvmaL;gF%_QUT+n3x`xgmb>^Uaj#R}0uv&9G?(fq># z29idTDk|fe#fxXK@7V31@L~^ngctji7rcn0AplQ16sZn)FQMxPIc}I zDS7-D2^k9*bNna*`%lc`#}TAY&xaO)2_B4DFK-N3GHIIH(!WQFK&tTi5K56DWv+CEhTh231!c(~x0`^&-qDH!hB&#RvNg3BxPJHR`{U&j~Xzis>E z>O$e#s=IwSF*gu3@K#jWUp5OsIb9GXQU2`=SrOb+&6Lk4A;mQPj6Kn0n|zx09b@i1 zL&@gbHVCi8VG?Nu#}T*3c%v4T0Avg$EweSqC8mmfuZ6~i8MRpFtc9PWMeK7zQ(?0G zX1QukRrTf*rK-14Bv-%%)gmcte4f>qneVxf0iDL;kQH4tM@{C>DrK7JriI+ZkhmUx zLN2w2{Uo`L@l0RCX+16$N^7fR1&U(JI({UIZdYK`9wVRNrXVD)H# zsUH~+6WV*?X?;0q2jz67LUfW{yciuo51T&>ZVTIzNv)q}WA zDoD^EDQk*U5=$YW3B36)@> zL6)+Ywf9l5WorWh`G3A=?tM#^7WkpR|NkGQ_uaWOXU?2CbLPyMGiOL~wj<25z5K(( zl_o0y*A0ZMf7k;Q3fA57uQ$y?bQot*g)}s76K7NLbc$&oQ(}}8&^Q}tTXhDhkj6ic zvthDeHEW9qwS@Cf!8EJP>4hsC}#QSQSp8iKAj!ulzLQgIFpaL8`N3fG)99Iuq z^&7N9!j>kakkeLmcH{n26C4-HOzAk!e`G@%z&%6f@PJG&Y^>)%Z1z({`sjzv?olb4 zwhpJQJ9C^jlicF?9N#^Cw)pM^xrB|3)PhR1<5|7HZ7DL>uibwa7m4Us>_8HUXn(~} zJiLFuEv}x~fQ#(*@5gcLAWDZD_ryCkqOYchhzyfy5>iix0m-vdiKfV@6!3dqRL`=3=v3BlLFCjli9FN5gofW> zHRpHS=B2`A)SZw{9rvA$^c<+;8cX}{xKFM3ZD2cdaG~Tkq3*myYy8KzPz@$K-n3%b zSzzn4)O`o%e5$@Bq;j)>7EUx}mH$9c9WS;)1Z|L(44`YWuA_SQpT?PuIX)O~*&{x% z5KRtxM>CYas|iUt9)!C+{+~JhD56&=v#Ul^6IEDo^{cA*S4qEBRQ0LiVdHHJA8NxJ zK+2?H{X6r)SnGE&UCTpZfA+5SYG=Z*Go~2?bMs*%> zn>gtx;@0rJkhm(LlKSF5E#jKIa~*&}(I%t;<~g1U&W0Uhos|n};Hh+JQ7|m}NW7G+ zavNoRQ%&T_b+Nio%580k=MR=QO4O85FP$M4UhJ>gK+k~&%gkWEtW8bIn@pFg6irx% zlzS{UM&^^dP|B@7O~d3rvWAU}=zfc6$ESK>VElp1IVm>_2aPW;@IVa`lVTrUrFE$q2{@DSfTeHzMU%H)aHRgBNo_JBvVNXFaby#C$T^(k2seY!V z?No{mLv86#y7@h(0+~eH(z0eHTH~qes-H2~0=+gaL<%JubHpnDZDTAGNuflu=B2N3 z0nQi^l!nCXAR*{Fl7uK zqSogV_2puvjG9d2MAX%y@qnXX^T0UOdlWouJ%ihqBdQi%Zm|%0njXeAZmpWG++1<5 z)w0T;&r_oCIy5FHEM53Z7pKJREauL|w%qy361sEw(5K+*k-4wPCMoC;qb_T$!DR~& zcacdODwEWOxMxkqJ*)atev?)gq{)qI8%yisvNb_j`ghrKb`Z1YoQgpv*awt9R`(zB z{I-^>Tjh_XANyur4g9fUU5m|%^CnJK7N-rnP7OJVGB!W1Lut z`%zv_o~HLQd%swAplPyy9GrK)uNw}L8C z$&wZplCXQLDs}KW5K>R@_A-T*ZvAkOl)t#n{=_H}Fp@SLo!yn35Rs@-%ATZxw zV6w+8b@1BtznA$`i)>l?tN1B~V0V+{@k@M_ac8M2J@Zz{L;D12Sz87BFJnm$3_?nq zMkW67Ha+VKG{pe%H-~5A5`U6V(%#)xr?nQssI$!`qimO)sft`#^sqZyriAV&|*+LGMlIVym^^@z2p)uGz+dXn?a&qkCqb5 zmqy@A0jIVi(iB50_%ummSbRTZK~sh+xXSw5{FtA_VNQy!;7O`4L>q7m9tt_} zZbF+GsSHY}f%>zurEds5%90qad=x3zOQJlZ04v=;)TrU8pVe>Gjrvoct0Vn0;i}&H ztr7EPi@Z~?f)HqRc9TLZ2Bw~OdH}|J>ji6f8omtpw85YMt_v@+(+z83)D`|O7!*8J z)M|L*GZpQ(ySBz+Y&I8lVqWXxL|4=Z8|OzjVk|1pu*N8x2p~I0jX&o-_ia}8n^T$D zR`YM*TR&>MnkB@RQ^fx|ZF0>Kx^%xrhbq(8U@NZ9f6WNA!r$1MA;Ra(85| zz5=4!D!4v)np;7&KkXwZl^SvB@tW&xd+4|SQ4QDFu?vh;9;FPgI(|gkrv^qUy-BZj zKv(&bDU}OktmwuoIR5XGD|l!Gi_v75?BwacsiB@&SP`fgM*yJkBtXY;(%+6_W{TIY zwqY1|$0PlbLXolI14qr&ldHwAdB0#ttX3^;q}e(xyQS;g!6z!Oj4@_6E7eHPF(wY4 zvJgmUWVy5pDw-$?hBd=X6@;wBxbaWKh#X6uSL&isbn{hzVaGT7AJF1pqsCZJIs6|= zB-0kXNTKWD9jlk4`h34xpIMNGV@&lG8SR7pA>OT*SA0VG0O^6pTCrM@{1}~IvV=AW zmRKwt5!H{uxY(7dl&gLlVo+;1&I zBznCu3Jl|bt@6hh<6%z2&dQ;nz)WCz{CIJOQ#|X*Rk#2<`xtS>k?2S9+U}4m5Id{< z9fSw6YLYGBvqK@E&#%QZDMB-ct@D4mMk%A#9BVD{|40uE+>G)8n$bq7ru6Hcr|ExDWyh6P6e5;QOwY*{ATit#Y=%EUBI5$wU-!Q z(3~t^tLZFfT-TY5zUxDE)rcFQSTMHd(|H58@fw-y`su3cRSGFN!JJYo_%_DV75g^AdGHR@AhySViZ9smz zLWJz65zkm}yKX7$EdMI4{~W(PZm?JR&+N(m*sD%05cx#@E8ny3Rs((~W}`ow`J{%7<2mlA zum_^;yUW@G>b)s{zV#q8M_(8}%R{2wmXW&w;`n6&Rq(2HBt?{?9R%rW$E~-(= zTTHd!d+3o4-~Z5E>Lcp|@YWcqKdS!daQ4A(JTU)Ni#mC;&f_W>2xo_s`f{o2xEzKF z@O;GJuk!yTwxF~Vx@-NLm_74s!~xz}9ofl)!16Dv4=64X=mU*E^M<_tb=RP8(EsXy zovgxvbguCOuw2Q&jmgL);)wp4`kFDeLC_edG_CcYCQF`Z>!t69ZL!?a0}ABa1;&&G za&9e5mK|s~oenXrJg?1KC4PHVcAV{HXbsflhpgZFj*t`I8v?-u~rS|m+#|@Se-`Tc3XmB%h_P0v$4!lvR2$Y<=-ThtB~BPDm+&> zEhs)o&vfLxb6xQ^Uj1`muxVlaa|6FDG965&IZPbE8ONZO&}bAusslfb*%=P-mOl`> zICJuLbC~$oN7MYKLoIOAA?m1@iQ#-R}MODCj&v?mJ@DdR_@+f?(Fjez{ zc4D!hj55sPnfZ0VPvHM^;6Etz1E;inHpzj%q!9cDBSZ+Cq$p=5#{%fwN|D3Ak&;<^ zq7V;RBcSO?!yTvs2uEYR_5b;1QBj`lPe1KE!dNf4mV=ibzSoJj>$H=P|Ne=U6?tba znbe1O`o}W9B{*?OsPN~Rr1-d`we-@OGmk&@td{R^-wkY5!`R-^MQ!V71w9tdb>^Lj zXQKH@B3oAZR}YiZO8{yOT-fnM>(}++g8UCJqNL(*Q|qP9#3ADh+zdM2X_+oD_%FV+ zceY&Rr5cN3saBM|S4j5KTO9yz_cV{ooGqWfX@Xb?i^88qGY35j3?!WfB^|ktU zHd;;50@v|+Pkdb5GNj{)mMw;NF7T3j@o%2Jx{HO7Y`b~+z0{D!ZA(ZkUSRz4h0CWS zbN9lAFFp;xX1P%KFlKi8KHu>h8|Zrn;_lw<2Kb&2)8Hk$VKqlT2mgvXNMhOajz34| z)GOM6#;?+;SEy+ePy*XP!S^wp_Hj&UK6E!;yj0|u2Nf4FUOweEPE@BMUh0x&FWor7 ztLDb+6JyGlu}dSC)I9Fr;KEhK>It3OPZUN61raff|`f%=27aB)2Z zF%{)){G~=*9xrCb%pQQokj_v`M~*x4qaN9jz2qBO?Jdzi^U$T|poC#PRsapw+5Yqu z!bdA^hSH{Xq!H}{e0r7wE-&-G<3aKuiahyF;^RRrn;|-o^MPEM=A|}Zd{|l=6`(Zrl`ppOq?*0t ze{5x)8cTc6N8q2rlW~+>>J96T*DjA<#yS98-f8N1J=!rx;A%gPUcOX6(qoEa$;OJs zLqLmODDw3uyu|!S+kN`TR%zoBrVcD@|L{^x72dEVUhUH8 zWpnwId?xX6Y4oy(l`FMDJY8QJvn{A%&`4dl0j91Ci@W2)kl?G+dv8R^#ER}jPw9`p z#&h^X$$uo~5hin!>Zar-)wLh}c$HT?wgRG;IuHF&&M>$5GnT6c+DCii#Y{;2q&-(u zBdi`6MfIAz4n$aF2)z@`|C*CXy&Eq&7$vryBP8gw#(m zM4KkH4U9}a_TOKBZzfkfb;~#&y?PDvS+>{iMoR?#Wv8Ia9uX4H@_u34*Nfl%%9`qp-(Ru- zviSXGnJAZ!Us5Sq{7&cXJMkdABS=H37sN2B$mckHhG25E@>>? zsGr(FMaV4EBM>r6_?;6n@zf5d0E64SC)(q_saDuFt`eOpW`Gg~Xykouq|n)W8{+p( zwC{<@dKeQ_lJ1a5l#}R?NQ^5M5{Xh}b4c9RO;QetSX%X^?$g51OD0z9;i|QwBkz;( zfoc~$EkO58x9^FWdRVo$TYBUr*Mb_Q+gKXxb@a4Ez0{q^;UaI?XMPJAQ;AC6;;BSA z56~Iikh-aYpN$=_x9k<0_xj-B?@WuO_GTg5)7`SIAfTQNxwllZH$7>nUxc&|N_&Y# zrLo$#TbIUBAgdg5zU51F>tV>1x%AmCU8^vc-u)E6Ql_VcXin2d(0q(fZBr%}wQRR| z^kgz2s76kyX&D+Gf6UxZ2HCU1gkIv^#+&iWzeJLB7YLR zGIutq)aCfasViYSbytT59v{PKe1m+pTVa*kg*a{RK~r8|D< z$nnb(m)<5G39tyaVJ;kgk@FygOFQ!xh6H zx_5KKcEpdiRRU%Nd_7AKC(Xadv;bMmYP-*`^*C6f&5L34y~M{ot=}imK{4^U78A{_ z$N!D8rgy#Sl)zi5k$55`WjLzP;|<=sa`>8+&-;fzL8js*vZ|GO#s9R*5+4se?^Rm* z4snY%_tKL$_iEoecNW=i%kKh{??+@WUPG8)`gV=PW$F^v&smEIjvpjy9qhaa?b2K$ z%8G8eA`}^;P!D1Ld}HX6Fh zb-1ZaC{Uy=EW+=pdB3LxzqJKDUF9(R&frNlKQG&|yx+_7erwzsCgj&(*Ps7$jm`9? z-`HM)>%z)OLVXmd^W5}9@U(;W?e4f`I+EN`ol30~_0k8I(ebx`3j;`*-btTj>5-p* z$7So(Ybx>wf(L5PNOUr(qK4OijajHLpH7Yrb4OkD!Q+EkL}vYoAj{lO`@KB)EuWCp z!FP>j{gJgSuaW8>{Ru>A-tY3f-_?1)$L0N=koS8gFw9H>9Lb6Bw}geW&zy)`!F+s&1ofQb&BP@%%<=E1P@WdA7E zY_cv-ir20{u#EgCufzs|u6ExaC8%bOIx!@>Iw}?U24NI~9rBwiXXtZ)b4id@ZSg;` zH772yytoRU`vQ>RzO5^r=z z+hzY~N?{>RMaH1C#%k-zqU{I3u<`U(-~SQHNc5gTno|CT!7QHMc4sBAix5jqs)(gV zx+!IoU`nZz`grlMe&&;C;xs_^np2*M)xO)ZAu~#}y&mYXR9$&2)l{wR5jY<{)qY5- zZlV$hN$c?Fww&LC7E^0K?(u5*6z%vqWOVZlf_D9R37* zCw5X<9nV}-2L<5zhDtB|F+o3MJZ<*BCu>6JX0xh?Xaks1^vM6u-*vfiEvyD2TlU*g zrmp>+X#2(TLP$qeap>LYFM&_8HlH9 zDJ-p;yxNtm|CB=hYj_W+62L$`NKr{7fW34{8#SOn)Pdn(ZM786^HyolOVnUFy7fZwVZL>!n0x;gY2G~U{4r?v(&r+?|RBnm% z?RX;E{!gC5l7}$V%u0KX&h;$kTfTI^4wB#xpo-e@+SgkD>hB`-+ABXXQb*fIISjf* zZStKKGjW2v}AeKrhwUDQo~u`i2lxyL$TNKZ39 z%8mS{8v5*oSn5o-;H_Qc`fO{EdZ3QaGH`#~g#_veRWYs&>HM5-`ezIhFoj(^Ql}{4=)fr39i?%o5 zcM?k#Gv~~gS7f2kZyC+R^Ec9CDark5-wWk_`=%7rwjAK+$CLl;S(^EeInY42C7DV5 z^b!YFm1HK9EuMTvcWYJ%3wG%_TjP&0zSAiiK$LfM>XOfjmaFAly}}kH%2q**`^Qj6 z(O)f-zYg>l?}pM1(nyRq9|IY4H{#q<8i=et$PA zbI$-J&fkoW+WHFD_k|1FXku@^k!1vr5EJd{`6Cz5PPa5JKp}d$_S5>H(abOhOpJ#u zlLPy4WVc@X74k#*!bhy}Fbj${avTz8C$)y!f#9Jk*+w zU#U~!nvT2qG5mS%JWUO08uoTPec{%n(xkhv3~YU1x@1SaEO8zT*d;oixMW8J=q39# zb*yRKiIv!vxI7#~h_ta%{>t;DB>p~Y{O-lfBxFb4wlV{F6)(sf!Z+ocW4ZY4)h_X(W9E73 zZA>Lw*@a`ncAK-5IF;(NCJbl4h$a7NM=bH6cm!RleyrN}aFfE_HzgNp*WF8>-)y$( zOW&RfZSqVTTBoKXEib?f$@7tWW3IjWBF*is*-5sYipn8||INrorMK)-a7 zVIl{g#~J^o4)sz8#+`$7dcWdW?ULvfKa`M79a!g9QADzYswI!hZDmt$_(w>Tk55h} zrcpJGTl;2FQDU@LEWDya6EjFveeAF&L=38ZYFKg6;zS!SfkHo+M4v=Ask{BZbmXOK z=en{S6c++%7Ww^$G{RD&$9c7*Cq&zs&3Lo3HUIWM!{8T_jEGrBgW_lwSj_9mLo1^9 zlr2vD900{ruWsWJy{BlZA?!~Tz;tBzwp5?Kx|45<6KFIfN<4X~yJzZm+AH%S*1+V( zG^A5$BkvYQ(e`bGMDlB1Vlg7#vOD|A_{wJE?l%5m>U&%SjA*-2qR#M_-J%yxl2 zn2@S930kK{haFZ~7aev+WphBj=&+W`3DKcfTUK=3afs5&<~&`RM%OVC*#qE1b>WA} zdCCZKLduA`A#a%F_J^RuM=zpm%?{*V>d-S-05I&I@vvG>8valy4>`nI`&6|3r;?wE zk3aLG4NK@d)+CEtUqd#uGW1rmpbtg=stUu#!muumr|+E3>*9vW zD1o|e_0q-{bH1tax|k-Ru|{gQJ?@EnEpjSu-w23@H(uM*l~_m`Yd{+Z*n=Z2 z``Wl;NZ@+urK!M%W_0M_uEYmH-mb(h04(Fp?|^2)n0u(ZRdYHrnzitpZ}1J|Hlv>V zWH7Y`>`o%}?zp8aRufS}W=^w|NHvpbJbA0bGN^VlBfOWqwVdMqx2Oi#J3p;MTrj!xV z$k}AsE^u5*HCE7eBRvCX(wo61Uj09A zz5OpupZ=%)U%LPA?c4u^t^DLc^$9bgwq2_H5#;hxhgLA7u=pHt4;9kgqstc8v8$78 zrq`w|fgtMXw#+cAzLQhMt%4()(d2JDe?=f3rRF z^tpd0-&5 z;N_bw7Au9#%!xGan9NMx!cGeCdl9n8!yohvO}Q*TSOZ%I8>U%5fHNrD+YjKtJU@Va zogYBfe`;O+0ITnV)Xtlrz#pL8@W0OnRxlTuKR}U7w~-r2VN3T}EezlfFqco}53qHC zKfs34RWh^qs2}-000BL8509OAFb{4>(u*G1ZSL{F=)KJiJ+j-}jvmkF(__H>?&S!v z{`R{|t$lMHJ@WRuSKp;}&vo?3+wWfH(%U2ldhK`L&ZmC&yKm-$?RQJ8iZ}@pg7J%( zSwy_JLjFiyQN%pXfIGlT-LYDnk}he(y1yu}_e)E52I6l72I9@(N#ra(wbpBU2+PEx zD!G9?o=OOsGY#J-XrYaEu>bHJD14`OMO|9zzGbRS_Q^Z+>DFny##1*!IuEI3p~et6#*=6VW4;)x(|Ra^hGhARbz_#ly!80b&0kOa6*9nI zGBH11kJ&Tiuo$M|LKZs(Xm$<0t)ALuyQ)k-N$aXROe9WuQgx?72 zexQy9Ylowp7dAvnc@@X1d~2txiD@mY#Te7SUs98p_F3EpJJE?Cj)77yL0rfDus%8b z=f7C3>y^X%QS$(Dct?HuZ^+@z!0o>whwr&va5Xl*S~+~I!JXmYhC?ElkUsCcVa0mM z;SB`$|5OfdiB_*IQD>*g{OvZ@L(`Pz5}X!f`a0tQQ5=j0vwmniys}B*czCzdNzHIX zNXCWCilAmlY%KVTFzl>c-+0&oWU}L-l$ZSRpb_0E`K+2Gn!{S$koc1XeLC{(0KHhU zq%suujEW|q6%LCpGJpKyRyQtG`+DY&9ZiT2G=FRuh2g_GAe?0jo(dDb0KQ05JMhjEO15;Um2E*uRB?o?n(T(*>ay+_mu)xw@ ziL>n&oQk^iZB0UPSXCRqZT1^$T>YGgP3D-N^H%!!n2-UhB93=Vi|$_DrTkFf?%8^> z?62lo)M~QP3Ii~ix zJQ{H8jJm7Qay!P(+JB=R7YNf!7rL?GyAfu07G>AZSyqpyj$yZPY+3!J(bj<1mfq`UYxHuQA3j&)P+6ofpAoJBr%_$}X4_@W{%oT@ zcZ`cMYdD_iKaK7SU!F45hLgX7e%1E3IVx0%Lp?^7N1w1O6K$7iUAWmWP8( zoP}N8zZ(dr6*VO@^|L{VpB8Jyb=7IShX1R_lrB&c`~|F~>CWb$J0<@S4380!I-c65 zlE6~YpJYm>Uct7(&xvc z4a@Z75eCFOku-B1n*|n6*xf7jUAh4Mp{o-oGqcTgW;#g<_KU)MpYA|2H$R?qr0JtI z+|(EWCmdy$(CRJt&r4=qnNME3h7cNe1<^-?`$Fjcid;w*jfpl&DOAb~o+{m@XFBry zt}+|9yU?yrBlLrHNILRV_nKV%Vb#02O62Mt@t*2+KGRC>2tNPrwA=tmkzcm=LJIukX71yisM;iv{Ecoz)mEJ%a&m!x8j1qqUOt-}l1jK!jS0wK zzIqKuzwx>8k047tHQHksY*MT#ZS(|k0&nnf^+z^IUfx*B0_D(gsnO%T+WFBdrtlg^ z?-tqQ)FzU}YCnv&f5?Ye@|dDnViAgz%OG~CkcB>Q|9!%4*A{l6S|b}kmELNsT7ER! zkHl-;at5yRzYBu4YEQn*#fo?Uk7Kt^kNp4q3bQ!M8F&@#-kdV+&l5~bsNYq2v!UvI z9kss4U^IgyRuK4qhF-PEqyGFqC#>JE_f3BgjOej=QGd(-GKNnq{-3|2_NywdQdJMN z{DfM?g32czq4u+KwL5=B{EB<~e}1m|FM|z2xY_zwMzj0xu15bizcq4*ivxS>{q7Wj z46geCLqX)k>Y%F8fkWuxrrI~7?HAD{Cr|rA{-E2LAX&dZ=n!zqt`6V20T#;ogTCqf zL4{CL?I+G3bQw9+QVmY$4|@1bO1VDZ$JN-t`iJo3}?-<8ExE}<3dpKyn+GzqgCeR_SC5-a@Wyf7BwUsY=5 zu^YL{G+*OmO;xTJ)q8D-cX0ja6J?jLtHY5Jv(TJCZ;a5JAssAT(k35X@w@!I<=-tc zzWlv0<4?gHfUmb$Ts#d49M&PZ7-z`;)xYLi=+R!uj6rSD_8Vx6n44F5P|tKoe8KY;Mbs3QR!tHv zcHUw~SJ?~))y`W?<99d;o!tL2=Pd*=9E^gP8;mUy^Gr6%K!3OC`DM>rxWY~~&UW~r z)2W~Z-$azPiDA7Z{wF4zS@&wh!{~@<-1TxGjH;b*%L*#Xvb(= z$1^|TAE$?hbj{p`BI~8koq$5qY=;^BpFrKrS$OycJ`_y<=6Ve~eoGp5dc>u#@{oFQ zq{x7lRP8@$TtTopX+sF#rFblgpSe&$%BV+pp9Ibcit7&H4%=Y!SXZZcQX~(pizi2W znST>>GM3<)u9D1esayDI4$-~Ob3zxayC+s!{(d)4XNk5*jS2G;d1F79lbb2;>dM0+d-om(4Dv6*~OYlQ~%qDd|z~ zzlrug3bE%EuhxmP8jl0e>Kh0@GL;Z&{g2b^fpasL~gsLC_ts)c&y4 zzhqxCMe3G~$OJ2a*Rgw@%B9`XCP7 zn{%{cQaBMDs0p!_Qq0cuR=d2m2H&Q&e$iV!J$A4cn+oss?sqzJD%s*mjtQl=O1`cJ zIXUbv&A|A)%nDA-4{>b{HI#|Sfg!Y;qP35=QyD5euomzd^%$e}R9XrLJo4oJ|?Rb~P;XYxET{ zOLt&cG_;uAml%#AdUr8UL9a1DPy8`3dbQ z{fwoXR_oFTkjGG5Ly_R&4@FAIKpc>GKf?vb+w8EL^s#tpk{fncL&j*wnR;Qk?Lg+W zqXWC{Ru~4O_lzda>7lzP=cV_2O1Kb8l_07LrJ5Hkta1F;MdN0s?--iinzVfWGY zd$hd^O`$2(T7luAys7rXmXX1_OXJk1BiX`GC(eb?Oqx8mu)#wVQl!9?oo43l=;W{sj`7VEn%8V{dqUogb}wBm zSNlcVpMai{VKu{_c%VYRA)tES0{6~f=V^yT7(@_|l%gOHVr7bZH^dytr{u%7cqBGaQC9J#cEmgCwc?^YB|?qxKr;{@GArfu3E$j_Yc zM+hVT{SzS}TSvP(Qpj-l<6np>15u+HYAATh1hH;!ImWCbSs}LB8z_7>4oA$1sns7_ zF~n?V=`Fd@GZ-vCL!rvbt?(;R82BU+_;n$YyVCuDHIfnw(VnuqNei?kps7POer%oE z;8q$T2P@{iv`tx{%M1t1B0T;3X9zfZFT>Y}vD5ZCAY&hUqs!fgv3K~HK8$*EEe|;9 z`@&&B+C4g0nG0pVFfX%GFWK&!h_qTf6fnAI0eKvmuF8_>Meq&C^d3EE?YEvZlV^|r zDawu(vqpEWnbeh%4_&D=rg?O}yd~GC!s^n6(hp}tnO^HIu2498OnLp<#y|!?OKx`6 z8nv}Hc6YP7o*aD|X6%=**; z1F^jAgZJq+HnHO{mBv9l`LYNxL0gO!{(fQ!ltV5?fQz}ZeKkZyN^Qiy7803xQAxog z+<|jX?e6L<7KyxmyboJ6^Y>5M_?W)FB$=M63qWe(o7u^Ntvr9 zkWOr_gCh5qM7o0*L;QG>&?p2h^ zJgy~cmkF=Te(q${wPep;G9Yj)hVpUP<8k4@-KBXH&m?W>8##uu+=HNLiuF*4izpma zzQq&PUs#;;OCLz-%nk3dN?v}sD8jGIo%-iG{t3=&;Qdv2zUC>i=AExuyh4!``#)cE zrEQdk=WA{z#Il$C|L*yk`!ni5ov#_clwWAVrQ@xON-F(XCRpMuqQ08*HO(Djp)Yg3 z#(eb3qW2tE!2#^Dyp=xa;y0Aqp9SC92uE}z@UD^|Np>ZdwLjl7#H)R)<=e``S$<}R zE!_zq9YrdWB`%Cd%^B;K=UK=4w}K>c{F=^{kNUrB&mAPmv#%fp`<#|+yF}{AOJc;- z5d;%%@X|VePoyi1EKVgko_Sv)5J@jf7XQ#1vmAAL!6rLWU$h=r>ZSL- zl6dEj{~Ms(n-DKvvhsK?WT=U!k1F<1;%y<2`o35D)2S3_ZQ^W<{QU` z`;^<8u;3-=9&LoBZaI#OXjZ*5wOrAJE`JT7_?AYmnZNzgm0Jbt3zytfyUdc+GM#sH zRE~B$qlP3FdJ29FE%}Qlt+9#jK`rNbiSEHI`|>Z>G1wHn@O1N^tcJ@Yu~b7b z$OP7zRJCOanu}otQMEFytf@_JSE0ToFt1J(ZK55cq){d27q8@?%chqMSVgYDCP9JB zB!1-1``Udx6^I!BvdhFAaxjS{{}DL9DR!m~?7@n#n*tjS!=@(-C>`4w{pky$Kx&7c zw|S&@xqRjM)8gqV#pnn)DfvTfxP^@+p34k}I^vz&Qw9au-nxpWW@!>;Ydx7PgM%zx z{bw1CQN9H8f6F0uIqfR{cm3vzwx42cUq~x_FZo=a!5aItZb|rlPadb#2>ecR~6 z^8Auge>@Y71z;eIeV2c+a@KyTb2>*0KWD3M0-lRlbOL^_;Sa6c zQu`c*XWm;j(XGz443gG2R{llj-#4@NddcF%+2zG6f738>Oj$Icpc9r}pXe-ZNRHXk zOKxC@_$8M((d_)Wb^Ep*XZz4j_w^&6Q~QgMB_SapU!G+CyYbqIlE-H_MZ8S(D7Ro4 z8NVF2tXS>BmhXAQoePM_Rx(!QJR(jL5fOE)(j5siHzMz+#h}=d!n~~$mCt4Bhgx5A z`dcr05vv$_J=MBm<=@C9(m^> zm%DUFkG%7cOI&&$J?;+aaRZu@=NC;!7D zpNq4i?YuFq?)n{_%wsGQ_oSr~ca*|t#nYI!`;Sw6a<7<@&l9CM+TN(XB(7wKGlpVl z?O4M-q@;#^FjR%_dsg;p&jfK$6;H0tN#6YXsE8*vnQ*R!kEd?j*ZpMyl__j2^0aXo3UCk zZ*z4G_rH>qnM9?`7fq=Xr6W&a`;Ch7n)4sASUOS?v;*|jl=hp2YcJX!10Eejvb=NI#P&)ov&y~;k7oShYzHzOimmj2wyLSk%*C`6O zy(kEM3!V4-J_VjZeWAcoX)%2ZL@(FC=u>DP5-GG7k5HcF;%8s_cDFI7)@JFuDj_5t zbs{FOD+^KvQq8QrXu%m zL#HTk_OSNKHa^R2VL}CMD4tgVT*zpSwu;sJhb)E#ZVAN-n9XQOFGe*Lm!Ya^o7;tQs?eVVgr z(R&6RG1HPY$aardqKS3?*cxS8dKerI+Nvz zWFZGY^# zkyLtQkL6*P!9`8NCdp>X+IlWwlCT?TDLwvw<{(ZO_Z8SQV!V=2k41&#^e-v}+wt+Yz z2IncR8;ExFz`pt7*;V}=l}_(^;)a@IUiLJ9R)pAOZdVgsP)<1?WgZ4;Ed7obwp@xy zBJ)kK)wbAuKXH?Zr*@?y+kh^;n41h6g}Cs|V((9W7cTfd>#&5 z+A4_A_UHL`*iv5WU~2a38hChVt(Pf_=$yMy?S^HjC{9jwNYUQXt)x)N)o(M?77JS?4)|{ayW;=qL9sxJxyu7K9i^ zZT2#@539T0RfoG*Nge;qvl#q`SaqT$cXA=;^=8!aavFq%4qwad5NVipFxhM)F0J-%82k=9&InNjC)r$$&>wA zKE+bMQ^r{84$Fv5YwxV5`_7ualGr;Ei%R`%UXWDTyYhiqWcPo17N5EryZMa<{9@}P zAplMsY#vMfeWuc#CiT;{>^9^uI-RS@O- zR4DaZhv#3~_<;Lp9-dlz_#5<(r_1i}yeALOQw-+C;JJT*=Sidlc&_z5!?Tg9gy$mg zJcICV9eXfr!@gFuYzz0il@)QQ*PXL8{UreXqgqHVpy|ri z7qj_8xU*H*_0*g*1katilE&s4+^%T*ST@7_TltzfkY7#@GRX3;{hs9F!ICtpODXpu z?K3MS^1~EeM4WjlKhMGRh25;I7`Dx*nP(fLW5=SA^*v8#n(^WdzYWnI7{X!+2X!b-z%Gmd+4%zW8liu zAy?rTZ~z=1xZs_Rvd57%mb$_5*6nRnR>)g-fV%%@=F~p@1yYthfSJcz8yn1v;H}1h zw>Bgt;H_2u+ow_GKx4ot5{v;GtHBufy9%vitnMs42lc`8#nyF<)e{V6{#ZSplrd7jPBbC~%5$5I{G@wws(=<4Q2bc))4 zMHAN7bry&Z1QXQ_l&%+-I}qWl*Orz8!g(X3iNxGCxWiZ z591Mz<8Af3_Sxta&sYIBLCflpSgeF$4~Olvrps42XfIUopSP}YQ-7d1K(eoAqO`X^ z1f!SFx2^MDXx-HFynqKd9z25(SpI?;tBLC&+?GzP|Bb+j7mduXk`~2IFWRZx1hrZz@A+4~rsSfP zzx7W&DR0w36ktcqg)vPXw2hZKym0?s*4lI=!mvf^n_irJ!xTTc-J>Fx?E+M((?r!i zzg97B8M8J7=SPQv##pB#k1l6RJl~|&daMj-M;{a1yX&QK=TD!yD_;71`|59z`;<8k0q9?LqMG?R+jsYp0o%9d|FC@r z(KQ3K?|ZpVu6@5h;S03yv3UcwujYT)zO(3)0or$H?vrcZ{2zUR_8r3knF087-2bqB zKc!CwXy3WHPp*Bh9{&Z}w`u2q?fYN$-(TpH0or$U?vrcZpfA$Ccb*=weY^e-`|ls% zJV5*I%zbk0`}Tye|FF33@Q4M2{tqTUc&+P!_sey`tLY1G zhxNc~To*k5m_F@&illY5_roRYXz%pC@H*B5?+@#O=kR7d@Fbr+vBkobhpY*E-;h>jQ890(ez@;hnTDc=EW! z22w%n;A&?UT61(he#?04h2K`|f;U^G3*h~+0N&KT@D{O>8IXPx`@lQA0A5XBc&+P# zSKbHS1_ki^qx!UWhjqbQLjJ<`-lLRk|4#1<@2N%WXz%1c@J=p(=kUGSEXzp%ZFm6C1mO#5Ea=Qyo+NcyTdD=N^@OxV9*+f%D=>El?Ywav*t zc4{$lc#ks^W5nK{4M00o`ilh&$#yfSkBn@!ke801qZhjqw;x867w{5Ko#o+Dr+bb# zyO0`JiW`35Q8UqkEFUZIDGZi9<0~iV`y|g6IVGxr*NMW6BX9oKn7nNzqc9g5$En~K zc$rPJII-He%+`JWP!-eWLAR1~<*v1IuPJe4A^pI-9s=9?YX%eMBlP8P!F==^io(s;Dd7o2OA zR~?zO<9D3%Y%X6*TH{w zrs7^5BN1_{gR>;3Fgi@YA#$Kb^$4LVmjXfq|{Dr|k>*>3Jp(|Br`4!S(W!oTbf5b?MvvN-48d!R^k31k0BBbB_yn z;5Noip1rDZu6XCyN*uY>EWLDGX*udVOgx^+59SXf+W|E>88TibD#A$G)}t5?k;^EmsM zHL^{+Ur5W3&`xfS-0Ec#3SxnB2j*J8)=Ej{u}v=wmuR%6N1eKo2N*tkgr2=Hl>eY? zY!xQ%1ZKXnk-|C4fEjN ze2Y7EmW7Yi^3G;c!c0F3=HD;D-!CGKO~&=&@00FTe+2y9sh2O!-&bmErbkWjdB8ot z5i|AXo`L!MLYpb_`TGZxvP^V|eate^tT{qDkH6QF(3iiDv{HHe?H^(o)(04VbXY$a z_T}$am93D!Up_aB+Uxc;i`rI(T3`M?Nf-wFJ&z*)H~ISpxHvd}*^9ru-+ey*93#Y8Urq40WO@}DT_Epr`=wPu?k}JJvD;tzwxZ1ytP!e6 z&>A5)9!q!{)HhfVH(k77mg_h6s`uUOQpYRbaEG(>?BZeU-^E%wk%~sPOfMZ15Et3S z!!f*Q`Ur(ILlGH>5{eXc&XE3(j}&BSA$am`pjxxK-r9SZn>PgVK0t11iKWh94q_`B z3|INrO|Vr3Q%NIb&gGEDyCk!w`0Gk4Z64P+{B(PZ_paoC`NX)Ow^yp=*{zdk zM+B0i1&TloTx!O_g5XX+$-A~GyEDU#y5U#E80~nF1q1QAY7~k(0wMg%88WC7ww3=D zA2rWh#=Ip5%IA2eG7*fOK4vk#p21QqjHj^&l2WUl<@$nq)M{o5Tgu4yj1Z}|ZrNtO z5LOoH&J|=yIFRg;XgcPyz>D`Qx0CsVT_~5As^IvK9ZzFYVq!|ljxr?ey zc@pEs69x9zyO^VKB{o0cXFrS^xnqwrxjpR7d$q(Xe%PzsBbpcl#p1)3c-@cpf!A^oHL#loPC9}TI-N=JRebdte}j0o_U z%xfd-xv&?WLMp%$N``4Jw9MRnj#Hb$wb+HUo2ynn8YHGtk0DF2H#YRBS_N@fYpC@6 zoe&hvFR#7I1B#CPp`M2o#=YO)VCg_Ui!o2!TS+z$@T}&$9I@F2UkY915Fs))1SIi& zk;fscP(cnM7X;D2o^SO>z{vH7$Dwky+&SSKfLs;GkRZ@B@5_{q-_+!9eu9R^R%DMq z_n@MpqmGp4X-*ns{w7&7m|==U*yYZgwoXi+RsL@%q49255K*Fx0e`;d(ya4It0L`$ zAgxX~qLa5_-eWCY?Sd=TkLolR%z&H|Ozlx6uxM841C4uP^yOkL5B!K7FGB@#LQK!=eyVsYnXVdmO75z<8?KKM&=` z>=W0xt3tp>Rbr~Bzn^Cjo=c*E3Y(?kKX^6y=ZwKD@h-nKK22_!-|fr(Z2HIp`gBU- zfvAVILW3aMz6I_Jas$(w1YE~t24hcrLv6*#Hk-Wug0;h?E(Y7MTZ7~0ZG&Ba4`r-IIg{Sb4plNq|_ldA_&qW zPWrimnQ;tTogtu@ccM~#5^>IP23p}X$;O3!nSRDIQ|p~^p7 zyp|s2fpAPhDzd+!PSsi)9dC6hv&=^tnPr)W!*<8<6JN$lXd#QXABI|~$5=IFO0AI(0Y<3165d_wLu7mFi1NwFr@d2B?S6QOL?hXOA1jeLG^0qxU~N!Jn7-OMAL zshCOWO#~tAl62$+zAL!>ERxN5qHi;lCmpHyr@q}^*=Dccr$GD@MAS+CCI`Zk@UNHJ zvr^4lP97IFW54~a4L%8(+vJRcG*1hqA|EVDKA z_0NYf&6mfG3G-44m zOD#jbi&V>ji9!VIxe6^(!*lO52DEvc(G-C5@Tjg z`I4BkvBFswvA6$*%@R%-tF_4?tBjpS6{d{6s~`J%sWVL%`>w|RW-QiZ;SI;VQ^c0M zs7m9N)lM7BOht5u{7R4|*%yCO5>Fi{6-RA>1*IlsnQx{c5Nb#lF%3E`Nix7q6gJ$y ztd8`jae;tgdQM+udPa<>2C2w&U#dl1rw9)&Z=?aJQC9_1A({b9BkF64lv=AvK2)>R zLnizsCRE5~9umcA61B}iVK+M6eBDT>h>qKAvXP7_^OvHh1_M?{3QW&~Hqr*-=>q~y z;GyUh%P7-F6A%b5)CBw;8b!}hpFOMUiT{Ks=%WNYZea42fbGbp30v4VP_tprpj)a= zDpU>(V@btw|5?pz1E>ROzFAv>o`b06^#|8CuTL%T8@7;6px*ZX#GsWQ$F=) z@%<&D?#RfvTXQ-=w{(IlC=u!e@!GY_^e5H16zi8jBRDunt1~EQgyb7VT77DaX#_1L zvGgUY`_TxhkV#PI|6C)uq#i&4=Rm`eRo&Guok!kasc_FH%!I?rBJ#n>#E9?EU7?(dgWV`2d!F5v}R8NZ3}7*Dt8+@&pC zv`+bj89&!*eLIUR67p@z3g~A4B!Pxgn$74sKliR%R#y3kORQj1-ONV8TL1gKvS;62 z_8o)lFOLz_tE?W1$5X)xK*U(7Ki@toT9QtO-R~|g3s;06QjwwJqJ{bPPFrf;iyqba zemuI%YXA4j(u*3q{ZZ8L)psMeI)|4$YM|$c8spU9aejNNOl7d7HZe~TC|W-uXnpqG zHD{c)y2mdAW6Jn7y|QQDUG`x?_E$_QGI5E@P+=-hs}c=T(_X3npykjja$0oo0*x)a z$jyI8L{fS{2fc?R==S&BTadZU-eh&Tq9P@_H;-Q4g)~9miabah^ZGdzqc}^n-{uvp zw+|>Iu(X#LQ?Ui(-7hj()T{Vr(aR@F7|pI2JL%lx&zy8d{p{mgIEA%e>8WR(LLi($ zfZ1=8#rth`lHf#j7IrqH%%On0=$P_o`z^F|anVM~P~24eS+rdY7fnlU#FO3LwXwCI z5kD&0@iGZI%hYi=HTvg_?58!~ieCP8)ETwradz|`IR7Ly=36Ivvqx1_R~&S93yt5e zj8F>4pK-?7CntZrjT)F5Q-R~xc3zUFA#@%fLWDA>Qfqd773^n^7?LKs_|KrH@a@pf zv;WEu`oZU$(aVR1_`HIT92K8??nd5h2nBsACJCE>#@pEHBCUJ5Y{$Sh3wp|C)7GNQ znw8 zOpt9KG4&o&Ts@`&6JMq8#iBbc);|P@XGh*k>mR3UP&m6O?Yo=h@}hG_fM;utJ9lQfC|2RBP)TMj}J8+%dky=WOzh)pv+ z{;yUChUy-FWC+xB!mPq^;_o$*UlQDrkz06)M;qr!hEQRt|1~Q_i&$^aESlioB-*4$ zZG-M4TZ{#nli$CtMzrZQ%3vey6Ns&^Bz)m|*(iUnJu}l5r>?*6-H!U;pGt!N`Twj{ zr)DW^!)CFTC`BVH`ZP|$Qm(tx9e4fTL z*gSH}N&k6x1U=&3ZImND;(*J+V+?p?J`)>G3&+~r%*8DiOUH*a+5=8YHw zzs|^GglfXj@98qK%bx*(7|M}3GzE8poCI0S5vBKsxIXL*xKRI0N4DosQ31Y(T%3p&D_zvk0-`kZae7&wkS$tz`U3i(9U>MZLHEZ<5N8Y`lpucE**gRq&xI01! zLvXa9bac957HwSW6)%lmwwFtcc1(u_ayMzjlaE%DUp zgyC70Ye&;COtIXu**(}o2iOeKyB)iMK8=gE{}z;TMJeD#$DrWusbOFRzSA;1KI|Pu=}2b0;zdDwYptz`d9?R}yn9UE(mf{gt*yD3=sK3WC1-Kr zOS{74;^Edz`97WNTKG;lkC%c8#zMr|C3I7E{&hE+Ov=*1y_ZRXWxF)np1iwTuxkWM z{!J?VQ>a#SwL!&|DvWtk8LNVOTQc7Up1(c0Vh<0;4b@S+T9a|Ft5RM?GDD?a zSEY1Y)PuZRQd$7Rj5_5~gx4a5LI1!SZJsFr=0!*QnO#X|n>1g(Vjvvwu$=BS0t7yj#;^oA`nu3K=oypfgq{XUpg4H92_n%N}b=T?gdpDv_oJgx+ z_l;ez5jS_xnCa4c(~+NFEvlYmLd_!{#JHp6N%{TM*m0>wCsL=2yHY0vBJ~5uhq2@? zm9fM^M&$`*8O7~!e3g#WPwOQYesF=43;2^XRF-3{=04`V#HWX$wCDz*eZ}AI2BEU- z4MMd_baHH=jWAV$ge*@gNzPaYn(OV@|k6>UgOM+D80v- zhWzInXD{w1{Fz|fI6EM@OHEVl+bz2ijTIHe>>WSIjjx|DzV-{`$&->NZy%68tRgqY z9@h0Yy~o%$r~Pp19|0DuMyZoPmi6J^vzQwFWtt4rk%xZv z)$;EgeBcV^wF}PA=ij5nzmM&y{{QOv_YG+9zu@0(clodL?@>E{N&Y>af?oyyK0E~; z*%E>9h4}ZT7r!k3KD1LI{|;l_?D%)nKKy&xdEi#Ag>^de*Q>r-{(Xoul?D8JZ%aP^ znqIc4=?Pyw|L#EF{V(|Um|_2Q{=Kg1OY-lH6#OdqccY)cBZnanz7YSu_S~1{-wnY# zT$o9JIF&it@$ZIx`1d-^4dXP4b8qUEUoHQBr1k9PR2i;=K0YU(e{qW_@|$*7|9|!T zdlEGGU-0h@-~F%i?{hnRN&bC>f?oyy9v~Ta4MOn?@$Yw*eOdmEfp za{s#jUU;Y4x7qrO_+zENd-#9${yYCEasO?lHilPW`t@%&egAzd^Y!{-)N{MTj2%Ba1X@cy8GegCFDHFZr`-6uM$lTE=r6YJ{#YW9kH$+=p6$EUxkS0~Bv zPEK|2>7;+0xaV{A9d71KhdN)Sep0P|r@T5(^_KplwmtF5^zW+1b@~jQx_{T1sL$4x zs&77%{wDQ}l{jbif&N}g{r(E|uff%KWQWzSh*jI}Jf&{tTbL;~@EgoMb&fy4^iJ*R zq&AW{XQuYy#4~1@J=xRhgk9(}0lT2j+NskRkNyD0UC$^78bpI7Pos(C6|xKU%7O=KJ8D zrIg4|(rsB<;R&Cl`>*qP`c~HG^Ym}Jl&9@xr~{TcDghIqP>Gp+^2zhpDs81lsS$qPPRXE%h;I`u&RPM`h*= zojD>G;)tAZ{#*Ti+RXV^eO&bU5}bcM`Eg0=&uO$iTiKj{{o$;W9u)l!M7(e0zdHY_ zfA_aKF7|ok8-bN4F4RZs_$j61=PSMQuOF!GpKxBtoPYf<&kN~~Xi&HRK6M8@*E=t? zRb9{bX#zq@F)aePO*{JUFQGF`Rw|h#fqoN;DHPug*YpVZJk8brlxAncwyiG+vvh+`v)x+eMJ-gLU zJ@p6o>NoAGcdWh9N&kp8^PfNKKT%&8y|=ENIZ?eCNB!Jr^C~q?|7bR)Cw!%P`h8e> zkB+}u8FjaM50>72^%>oGz4D&=*DXJNuAW!ye~;|XJP&4mpRRhM-ZdQ0%j(@Wr6;)f z@38*Cx_UQ_dMZ%5ME%nv{dIegJ|m)0z5Pl3bZXB$wHJE^pMjTJ|NY~+NI&`>JG;|T zJv?(fU$3qIQykA|*5f%-)xG1{X~JILp7%N)Kc=(wpIXOsUA{V|m&s!q(Hs5OF}>$s z$5j7(R3Fz*{)^)}^$zd2{>1;bk86(t9#?&)|Agb(QSHf|DmB${#r-q$(JepwPq;6t zZ%E891z4f~E`Lqjl;72x!thqFQuX&-QD6OudW(^IzwPliTzxWLU3K+urK&H}$H1@u zou@j?$KTvFs=s=%>(Bh#CiQC+)W2&|AH#ZI_sqMx)cefw?{d{!Lex(W)Ef!)JIeIi z8Q##3ZS}|MSx|pbQ9p54_y0Wy^m76Jt*QF$kdKzCfBfIsPu+9T>3W|_HBeVkz4clB zpT3^z=PLSN9^DkdUgxOa1g?Hg@t*f*nWV}mT#j9I%>?xslnpwo|JVDg=M2(+UR0=_ zNd`nSSv?}0qK#fwUoM@ZwO&>)1Dv8|UM{6`ftS@QIj3m4m({lfPEo#>%j&FuOD4Cc zzi%zd^Rhk z3%y)L=Z#+0Cuxe-dRf0mr)Zg%tLa?elz&zq~$Y>s;0QbM@#@*Wb(PSx8-fb$4UmuXCZ7>*&1E%XM{L z>*abnFY~f`*r@C8W%bRuy8d2PCxO)U_p*8stFFJ7)i((0`g>WO^iUy8d2n zs&lTF)ky|*{k^QtC93Q1W%Yw1b^X1pPO7QvuO5WF{_5K#b^X1pzN1su-^=PGvbz3W zzCh|- z&UL+fxz1I++(zddFJGbaLH!yDkN2R?g!8RrcHKyv)lTbT07nl{!!N zaz~x>y?m90H&zU3AXza#x)Xs@Flh zJ|Ue8y?njS8@;SgWfiUUvOYCiw9Lyl>RjOE?mAERvij$X>iT=Rr_Lk1+)L*?FW;nd zH!t6;b2~5h);ZV9x9D8g%eU%W)ysW!&hc_zoe!#4)4cv+oeRBuo6Z}(oTu|zFZa`V znV0+PT;S!~b)N3!0XpYvy z!*$N_@|`*#)bC5f{omEO(93t}ywS@ebYAP_yLDdX<&ioUczKl0)4e=e=X@{Uqw@$a z->Y+;m+#ZLo0rGv+|JAQ>zwQ52XwCM<*_YVH4 zsXEv7@?$zz_3|{GbG-bx&Ii@ME93q|byn}4Hu(vi)rTjWJY8q?p;0D3sq-=~Kc%yJ zX=?s_hR)NyJX7agqLUQoag1Ib?)ZnXLN4owSb-nzo&Q-lUPv;yj zKd19S^{cUY{bM>8dU?Li8@*hh^I9)Iuk$i5FVMNb%P;6W-OCGg&iC>nokw{2MV<4! z{F2Vyy!^7x?Y#Vo&beM*taDv2zp8UpFE7zK$IGwjd{F(0G+zI>&V^oHs`ExKFVlIg zmzV3j%*!iuF7Wc}I#2iVN}cn){D#gWyu3>1JTJegb2l%)rE@zkzpZnwm*3I3u9x4{ zxvG~}>zw1|H98;s_4xHq=v?UK|LDBY%WHLB>*e=!UgqWZbuRGo2Rcvp@;aUKz5Joh zBfPv`=R7Zeq;oeff2?ymFMpzQu9rX6xvrN#)48gbH|U(><l0dwG-2`Ck4;=Mi4staF~1x9Hr>%UgAB=jClW=X!a& z&UL-KL+7eq-l=nrm%r8d;DO`UKc#b_m%r0_qn8VHUhC!WbzbJ>T{;(d`3Ie+dwI9c z`Ck4}=Mi4sqjR2@i*)Yh<-Iz$^YTwR=X&{Po$GpepUzdiykF-WFaM(RLG}BW`1nuj zTUOuFAo|pg9xto^{>)g)E zf9ss<Cwxw6g&_aDFh`X7cC6?$2H6sWrXUe^C~qiC&{Pt$psm#gVq;AQpkt?K%FxrWa9 zUOrvt5nisTbDo#a(7BtJ&(yh{muu;q>t+2W&!W0sK3nIiUaqZkj+f8T`QX0e*Wa&m zp_l9EywS^bbzbY`dO9!ja($f(yxc(N>0WNAbH0}w={&;A=jxp2W&KYZin@8ZiO%i3 ztUs@&DA&v9>0H;#&2+BnW&OVLq8u-`(D|Txvn}uc0i6rIe7?>by_~D_S}$Lq^D-}A zsB?jrFVcCsms{$b@8yei9^vInbk6g#eoJssH!ru+xt*6=>zwQ5%XF^m<;!)h>g6^% z=XhDa54q@|`gKmc{`y~N7ZrNBtc3#%MleH+<%bj(u>*ecouIlA3I_G$~tIh}a9>4w}oeRBu zz0MoGtpBxk(ONIxpz|^>>z|ty6?nP3&eOfD|G8sPzL$IIJi^Ppbk6g#{z-gMH!t6; zb2~5h);ZV9`b`Bzb-k>AxK~uw%YAgt@p50C4;CH2{$ZU9y?mR_8@-&T^I9+W(|MVf z`|Dib<=b_h?&SeG=X-ge&Lg}$Nawuc`B;OM!}O0g&*^~k+{YR$5qY7=^F*F0@-&er zi9Al^(IO8Qd4R}$MD8JS7m+)P+(zVN@M&zSI#O*J#dfSurc-$@WPLVf>yg}r3BCi&CrN~P}UMTWBk!OlL zP2@=;j}v*c$iqb*AaWm(dx+dc?{jaFGXy+(+adB6ktFqsVPU zZYgqeksFF!TjUxdR}#65$VdB&+h64UBJUP?r^uT`-XQWikynenQsgBfFBEy6$TLNr zCh{ba$B8^zACY^A+(qP$BDWE_rO3@iZYXkXk!y%tN#rsjAH7Z7{vz)edAGww5_y`mqf#M@jd<2S*K=BbMJ_5x@p!f(B zAA#Z{P<#Z6k3jJeC_VzkN1*r!6d!@&BT#$ww5_y`mqf#M@jd<2S*K=BdyKYRp&>Qh4W<^S+*{I@s%fBXLbx375dUo1WX z#Ydp{2>f4q1kUDukJ6t7p)Mt`IIBedTI53_pRPYULS4+?Z!PkzB1c4iPUN*Be=G7o zBG=KMRdK@h+ld?&`C*Y4iTr`cg(BzZ&%!uidv!%_C-Q9~=ZpND$Zv}LrN}>ve1`rk zjuZB$y~smEeq7{bA}2-OCvt^n?d{bU`AU)diac86IU>hJ{#fMiM1EC&wu!o!`(G1r z{|qwMM$IY5Red7fw3xEGq<{O#SFZ<72|4x_U57dXhW%B!%BdeFV znQX2%|9bIKd<2S*!2g3IQ0^=93}BZdeEdbZ#<<40Cb%ZKrnsiLIyYgikE@?+fNPLz zh-;Xu%QeC^$~DF{&Naa`$u-3_&DFV?+vn=%8sHk_8sZw}>T->6jdG1~jdM+KO>#|f zO>=d6bNgKVTmxK#Tti<wSgau2HTru5qpju1T&bu4%5$E!;j=Ki2@)AlDGrFjtpr zglm**jBA{0f@_j%iffvyb1S#c)z3A+HOMu@HO$rJ8sQq{8si%0n&6t`n&O(~>h$6E zx%#;VxCXg~xQ4m9Tq9hgTw`40ToYWATvJ@rT%Eq$K36~20M{Vb5Z5qQmurM;lxvJ@ zoNIz>l52`fhPZ~gx?Ce%qg-QL<6IM5lU!3=(_Ed~xP7jEt^uw= zt|6{rt}fRI*C^K**ErV%*Cf{z*EClrkK5YQ{agcFgIq&g!(3gi5w20LF|Kj039d=5DXwX*PJeEntDkFtYmjS*YnZFc zHNrK@HO4j0HNiE>HN`c})w!M9=j!Jg;2Pu_;u_}aa*c3}a*c6~b4_qfa!qkfb9DxA z`&|8816+e#LtMjLU9J(XQLZtrajprjNv6AxF)%#xTd)}gSmaKey#zo zL9QXLVXiLM2-hgr7}q%01lJ_j6xTFYX9%~?)z3A+HOMu@HO$rJ8sQq{8si%0n&6t` zn&O(~>I~)fx%#;VxCXg~xQ4m9Tq9hgTw`40ToYWATvJ@rT%BRuK36~20M{Vb5Z5qQ zmurM;lxvJ@oNIz>l52`Z z=k~e!xdylfxrVrgxw>2{gJF$KHRdsPHJ_5x@p!f*<_m99; z?c2BVpW8ie)bNp`{O6zF{KDpeX6KK}#Pi4H<~9#p*qH1%od*vedi#i0ezrSz?%+RP z^_vI&H=EaI6-zid&V@Xo=4V$wo0W17{Ojk*H8TA(otl5z&v*5d=Qx!ecc*=3?h<-T z__g$u@Y^!;&vGJvW4_)$eYr#ZuY^<0G3OOCsQ&LM^*_B`|F`ybW6Dc7)g5!5QpL=r zz4}bs5;?X0wf}3>pPuacu2-(-r#Ro7FV!D~r#($?g7MBVx<7^eF}^^ZxYhk=A>5o7 zP1ECabN;lks_A}`ZqBm~qNnNR{O}^W?|aNQ=V{B8!W`)X=mGZ4{=7~P(am|=KeG6R z>VW9|46|?c-=#a<`)mJC5^?d0!(j)9&%;P_io)B~1$l`nH zX))()bpl;)H~2I5=V8vjOL+;WhBJ9K?w@}8LiUrQ{}21|S$6*-bzn2cZJ&L6J(9(@ z(Er1!_RY^Nw$l^BTd5QOdQOTynGd{Y>FzJsPjxe8E{)aI$!wQC zjz5@~sl0@v?oB0droR_`0sGED%rX5+s;2v~qc}dMUt4)*{%`0fxZTI-(cj_U(95eI zB-n<3itZe?=N#soLt;+r%9tbj zW9c!@nZ@m{p$GrQeB-5j+@E9cdg}41FF(?qs(@G2{ZC(7SHW>k{e$D2$f)C7O^@;U z*7$Ao@DaN|TDd+Bfx4LgH}5BN=!r|wujN^w?^+`FUVp^>r#IM;&K= zed)vgM0%9Y@y*0Ve$3r{4MB5g%{Bi!k^!Y zep>iV+u;81aKAPCbJ}+9kMNJ^5#bYdpdS~0@lJS3_&&OiUpLKmpI1MZ)yF3&yk`pT z(#`X2#qUn;Q|JkPT|39zzVsB`e4V|a5OW-U-7&tE?x#2AoJYP#Kkx&N+j;b{yWnAZ zH+tD0;0bzf`flSzm~#QW;g9J1cf(Epls)jsUc3LcanYYyguWyC9rnUgqTlEzcu@4W z82{1UpA|o&pAh}Leejs*U%4Od{ABO{pT_st^Edp${So~!X?R%lyB~l@M8D)gctZ4d z8W;VkzoPHw?P899zu({vUssshtMc#MuDBil{0koB*GKbw_{?E=ieCrE@OE$T_sPAh zaem#|$bQ}t_J#k4o;ZTzIhXxoS^TtrFem(v-EW@7uckXk(O<&(x6F{m@NJjJyh17zl84BFYMLj zQ_lZBiyx(@+26_j8R|Pb{k)ev1#^sdp~uU^x3fPui%+CSE1<77m$|%cydqpZY%`Z{ zviPs`$f@YpEP;M~_3BXXr}WF|u}bKdV}E28kJ96n?fHx8Ngq7GIZI1nyYVyN{kZ?X z)BR_{chhewjlT3bbYCs>EAaJL?NiVX)Q30ZoZeY{Ha*P#DE5EM;w{T#ev1A3c)k9~ z;%zFRA8v^Kd4TgL&{OpD@yAZHioCu}0erdRyiQNHu)V>l=tnMun>oWObGvQeQ}FP1 zl0JC4Eqns^=Rc>xqut?~@$%FuRSoXm27i!q>d`|(;fpxuO1d))K0tkcr7uJ2>HhGr z`u8;?PPOiW$}Gkyj=~>S%d34 zn6Imc(*0}U-}|utbLqY>;lJ|s+NS(u$Kglz-R^yDq@+xR&B zk{;;;?|d=(HPwk7JwKNo1Up^*xc`KCeR(%@=F(&It9gB2&*ERx)8FEXUcvo2s}AN% zzp@_O(I<%1MV}Ybm+JMoKY!Z3pPo1bzl!}W4bb-;w*C2robxxlFZ=n8;E5x4zw^1A zbJX@4jXCF-?J0WP!ErYGv%CrVP7d5$uSrcgr=;!ooCi;of}8yu-VE+9WA_I&heyiV zetQdes+>LNcJ+lR^LVAH*$MtaD{}b<{uO5b( z%S?KTZoY3?O^(1x-4b?en0yDhvDkm ztIXw1x;q*E4!v?6^v|0DFHdht57Gnle)L!r{U5kLv*^j`aCJA!Tt1+`__RI$XL^Ee z=ATg)`xBjyzS+-7^k)j}IjiY$`aEv;2z|%`yI)^@fzbDd2>ns^yV0Xd(7!7O`~Mj| zxD;NS{ZM_(i7$iS$m5ewcbCJ}(@y46vjO_yRd92=f4tMUHu8a;XhemAdg-g%fGI0~Obuhk45J-uvZvU>TQxh$r~ zYQkgm%bTMgJQMyjeIz|q3w{ngrv>_c{R0$rG5fig9`?i4(|+diB|TUlUV_)lryltF zI0sw6m+*LALr5DIio8$aDJwdO?`3=>HT)kaiTl8PXZaORI zp>}ZdINC#Z>4SN_>Z&`Wo^x3TyWgE2qQA-hH2ScPc7H8BLLbTfJVFm&jsDy8i`5At zz1;^o+j9ofqx4VNpHEM8Lw_E^VR7WVgB%Y?S3bEls=fZ?}PL<57_&&j2@z9&kCz2>{~~xU zZ?7Hn=q=?kP4)DZxg1iie-7xLf^Ud)w`hY6Mh%nFFZjH&@1rxOj0kH zGq3;Y)#;r=@UZAtxgPF{`A^X!bhH1rcSAoa`bG4Z=)Zjf`f++a?oZDf;VCgEMNf-4 zZ*)iBnQb4pRz2W8y4lYP>KieA9Q>j`jUEvFzP->7ivBKoNc7*h3H`9>x4IebihhOO z@QCQorbk8p?px50iT)9KT=c)Z75#+h_v!;rihfh|ji)|7Dd9iU)54z#qwhS8BV38M z;~~0_ZtlI6mR_|whpa&sO$Ao{=2gQCB^ANnEDAE2H9_585tw^1*2wYzllcwa@2 z2yZa}{V2UAuh+};xbOz*3181o3SUA`3%_^}`o3py1kCgE>-2!|3)Bk(JwHS@`?H4b z(#>(cXbAcd;cwETbaVV4RWEe){21Ll9-9nXmAKe`PBXqy$C!at+Ao_i$!-JxK*^}^)@b&bt@XqQ5dFJ^?_$TxT z-R$SM8R$pp=Ih!vdQ8l@WhVM@;UCcx!n@5vKPl$FO;3q_WAzP--v2b+?B_Fd=Q$jI zv!A7(M&C!rpE}NDx?gz7XV4D_zmFcIo9)({gMNr^wtEjfEav<|cZJ_D7jq)QzobXS z{0p8%KPLLK>2bQ*Pe+|#)WDu%v~ZjR4B zx}R=tmm%}f4+#H~9u(fI0R51d|1Lc&`i-7P-=!blPkKbm`I{aUbH*&doS5+6=y5T> z;05#(V*X)zQp_2>5dDcpI=YW;j&q9_(f8BMahO97h&d%*LO&?x zOs0o~mwg%iu<-G8mu|M(;uZ8GbaOv>m>v~#N-jn}CVVJ8F8mjILd?J6Rm@3>{yX#( z-R$SNOVCe?In(G)0gkXa&Xrz6-zR($-7owvdVp@W8;fI3kZz980eVQx8L$-nu<#G* zuJ8`a(2t1u^XXC1FS#837~SmWAbMQP`H-FvbFNx}IZ5Gb=qcg3ucM!)o9%9)JI~wq z|0XNZ_tDMm@*LeS+<622fbg;OpqRgv9uob_R$)$1MlM&;xXHeCn-3KPcurO%DnG zn;sT^--npviuvErBck7GJ^E3)+0Pg~CgwOFp&u7>BJ_mtL-eHZp&w&Tif*=B^%HoS zZjMhsy0g$e{+sDO;g^1jIey_Q=>fW#f7)m02StArJw!MAxs@Ikb1vP0Ij)%VK0PA* zs?X7n3jcr}qnqvClSDsGH~ar3Jt5}Y@CEuw;cMtA;a6=$KP~3JMt2t3$N#J^(f85K zeom$P>1IFwpa;a9{I4)4D7?(q@R0C_=wZ6q?j@Vhcj;!kGw2a9r^YwvM}<$I$Ankh zjDB3qA4N}y{`d4G-R$R$TQDaj=B%No#hi9q(RW_7kN>-LpYRK|q3@@g?e3rl=;rvG zza9OcnDY`nB>dDJ=!b~YZuWmBJuc={{}KI!@CZFAyzCzIQ)2#bdRp|i(w&!a z{LOw|U4%J4y4lZVbUz(`%W*E;i+(`(5_(X0y`Rtz(am;0rHAQeyXX9jzANU;q(_AR zMUM)T~>}LTzDdv>?1^twmb3Z*Td_UcJ#XkNs(wO6;o8z#X z?x&mM-}?ai0pah^gTgO8h<-@SpGgmk{voLq8^bD?Khe z{5$#yy4miTf54M;b9~0pQ({h;KhaMMA47K*<4Bt0S^g0EKDwDdhVB>rU+4k4+0TK0 zVNOuY*+LJAIRg))9~Qok?g}6NH~JB}*>1ff@F?Bve?C1X=9K;i{kZUv^n~zZ^rVqacPoK@kZ!hHE(ac> zo9zywhsB(q>8|iwN@7k#_#S#x%)hY|`Z3Y}fF7rt{k*g^`Ux>-5j`p9G%tgGO8Bev zwD3k{(RY^M2%GJGP505w@o82LeLvmYE-%po!mFHueo*)fdPvOwiyjvJyz-di(#?Ku zphv`<3oD=>6?5L8$AssqH>&CTRb2Rs^aR~(x7(@cC+X(+ETpHzoZ6MpPYchdJFnqL zn&X*M8GRq!%+I6yMgK#3fNu7)g%5LrV$KuvkeG9J74*Zx=hI!`{;KFl=w`cL(xY^< z|AEuckBK=;>2cw;)xRgtoc|ZTh@KSlebv!ViT*fxnr`;<7rGP2kvIE!a}CV#(ar7h zE8Q=A;_2uIg!^j3gLJdqBt1ko+ii6Q`e89=J>3=F?o9L}!Z*{SVt$ue=*L9=BYK=} z_VcQ<&`*dtYv@Tar_0&sr-Xk^PYdr^8+~UfjEOE>%3rY`ysF=sJ7D&|~V5B-?%x9D-Qx{q$=*KLHpU-TcR2k2%$kI{o-&fs$~CnVGj+ZcUU_)&UDbXL&40FrMGuQP{m)0= z6`rI=g!jrtKPu)Y=rPf6b^-cvy4lZX=?O8X?1ktj#hfTTCA`W-=%>Z}p>$^jj<9+E zSP|V%H^={mmY5R|zJ(s7oA;M?xETEq-MkNQ89glKG`$3USNL=E2;Cf?t1d-9N;kLT z)AX2_Q@IuTap8B-6T*L?C&m2hT4PR1^yBn2-Rx(L%g}dTx9{hp={~wS&VSPV!rjX; zCqOsz*V2P@_0KdimrL59?}|Ck&?CZYUV(m;Zr%@kKRqt`IYIOj!pGB-!vChH=w|<4 zYKu8(y15-ow1Yb6Q-ms5< zziZI<(anB-OZU^wes;eW{eYOWogNh4yEFPB;a}6kbhF(F*P-vy&33oZBVta^F6c*v zuc60;x9^I6T+ClYPl&!Rgnp84_VWRHO3W#wr^TH9*JF;e%0B)(=|15%bVJ`yH`_fz z575o=>3ReDK{00?JtVyGjp&Dkzesn*{IcEAkBI(IdX#SVb0a+_=3LMNbK+vooAiY6 zi+iG<6dtFi=w`dO_Ch~RH^*lM-FeeK{w;1o-zR(~-7ox%o6!%@&HM-GLDAnu57Etj zcI}NhVKHX~-4%0M-hzHa_;Pww%LikJcr0_sr^iyK~EP7h> zONP;R-oo)W$6*BBM>ofF8{JRG-*TMZw_#2|_?Psc@Q!)thv;Uz`{-f1*>0PD=(}Rh za(YB~wf^Wwg+D=$iTV5KanTRmjyVas+0WPMNinC|0Q6I0&NO;jc*%k2J8#>^e=*%h zH^<=^-A^~ifAk>C2?*ap4+`%!82yl#{{}rQ`n86j@6ye2$frldobTyTF{j^9%!vu# zMvn{cG7S9$-E8-FdXjFAPscmZPl-8i)6>Gw8jim64vwTbo=?+#bTj`Cx?l8r-HAB? zy4lZn=|M5)EEoNdm@}6i7GCWx^j+Z((<5}V-R2|EkJ8QlKS+;>Ils~4!n@y%ISJt# z=}9ra#Yps1qW=UvO*i}b2i>@4xoYV`9$W`!FXiypWy{-eU~*&sE`+k1z1L*sNFQohFW`3!$=m$i9AU#Mo`}r9?B<3_5hdE&}=T*8ZyupL$ zM}*I&N9ks}S47Z{(arux>2Wcq^mz0W!Uxck!oR1d#Qb1B=A=cxfbOip@i+Tfb^`i7 zy4lYGbie3-JQ4kX@aB`?LE*C=hKGdzF&Q2f-scgxD}3dn@QCo!AA?7QkC+CJ3E%uU zJTAP|6Yzxa>C@p!;YXi@r-bLtfTxA8oC$Xl_VNE`7ThO%(9>|g@M_P%1Hwnmfd_>b z&4q`A_nHR}3t#^n+!Y>}5040cssJ7pe&~64O!$Zw;Bn!F3*iajSH1{O3SaROJSE)! z3Op_Rvc+)cKlbsT_bS{cyv%EGzwmK!ctH4$rSPEe^OwUz!XIA&4+}s1I@}fB`we(R z`07>gsPGzZ!DGUQzYUKI-}nwZA$;I!cvATKHSiSO$MaF41UyYQ&nJD>!kx7^{_59g zXD%PpeRR{`{66}A;Xkc|2ZaB&9v&24<`Z~Gc)8EuVd1BJ4tIsu`~n^ke(sm>sPGHF zhR1}r{01Hu-gyf=A^hfT@TBmbJK!ndoxX*qh2Q!e+<6bj-yG+7A>1eY?Okxc@GZOH z0pSPtz=Ogo{sa#Rue}c*7Jl9@a94P%1MrCOKEJ}F!tebZ9uxl1pYXWwd4ItZ!dLta zPYNIN4?HD&(lK~i_?_w(W$2%GIPcrXe|$-}Px$kt;eO$Z%fbV~Up@sM6uztiJS6bPxrd$hlD%z;1S_n>2cvZ=_%nC*T)>+dV9N1(1XGoHbCDM{w_Tx zym~|Qlfqx5J0IEGy|@wj0pYXhVd1~gqr$I07jqKA7t+(h|D^jrw)f}y#+VZlzKR|Z zUcCwWap7;#Q^IRBMc?;{z1=(MLE*mh(07IRrN@M)=}F;NHp3j}Q+vBF(*wef(Zj;0 zH^-c)@ZI!;@HQ>bPYeHy?*Gi*pR)t#hlDStM})UNAN{!SP4txTf$A3)Wqz)`0o#3w z&tp$qz&UjFYqBzzrs@NfbU!V8K0WX`=9qq^i_mw4N9b|k2k2?xc`Y#~khJH2N_T~~ zyqNnh{CRp>c&$q~{|kHmB)Tg+O^*w|{ZhTi|iwANGMKh0hDa)51sP!F}7r z{`ZFmgkL!T9unSY5Zo1BW(YhgeA`fXT=>d6;7Q^0?}Vp?-+dR{x82_V4tK)?!t0KL zhlC#*4R`74JB`fcyL;df;Ro+!eenV`E-%vk!q0dR{UDvc4&RB=Lv(Y!9*m$L7T#w(+@&|+cF&p! zkI~KbIzmr~Id46Lev*C_=M0+!_or~Z%=!79^Z>mI``sQ!KP3Epx+~`Nnv8ygZjM9F z6nK>WG3S3y4}XXKIi3E>Bj`&n|0w6PuYSF4<}#Qb7jq_M@#pDDF=x$G%uf|!f7Gw{ z%v=^e2KRptSHE^ObE!BD9-{Z8zfX?~4?d25d=KV4!TyC&&MAUVrZ1wW=;rrQp8f>- z?q2lAvcHF(rkmGkw@ybt@Duut_=_ofKM4>2Z2RBz=stK`&cE|1^iy>8>%lXZeRO9( z`hDpWXK+6K4f-Lv{}=S%roT9o^Xb>q+s=Xq)9B~X-=)We-#Qz8{{i&xVE@M~UjJ$I zqoNFh4E&y|VZ`diW1}f3{}v8qZ;V zQuMoJ@oDtnpZ5GOvUs%^=Ep_9PZocX?muMD|2c~X=3{o+nzrsi>K)^(Qp0&=1U(& z_Z_k4ugKy@=n>IxzYz1KKSEE7{-;^I(jv?W|6}jZO<8;aJt_M8v-l-1Vt(+bJ^%hJ zzM38v{VFeEj`aR?|1o?1qAb3b9u@uEmoZ=Z7`o%&_tKlMFK=e?Lv&a4+r5JM(x=i> zqW?)2FTWUbLM80|xju`}rYA&yYZgE2Rm>0M*z@~l@dA2G^nc9a=Pki}UrBrZuq?iq z9ufUtvUu)mn4cE?FqJzUD(A197E(%aCJ?3>qbhfKdT=G@2EAr~#hoRsjp=;1PU zf1T+IKXn=Rr>xz-fu0inxapU(`=6M;@Y9xa{wa39J3S@*S<^3X_qUk7@Y7dteg(UK zD?KIrY16N0_cxio@Uvg%{8R0In4S_o*Yqpd{U1$Vc*B*PU)k=v^px=Brth=+f11AV zR&Q{A6}z8LPYGXR`c>`zQPUURb`|HJX7?YYr-ZLI{c3i<^qZKI65f^`u5R}yn7;50 zreDME*L;ifg%6~MPq+IEO<(vf)30gw{cm%=@FDc@8Fv3&(-(f&^v|^WSG>dd!XKoE zYuWv^rcZCq@7I>Si#fit(KlXwH9S}w{tTaI?oM#K^aA>bweaXU=$oGl{6-J>Z6Ed? z`bpt?>24jnKj?k*eRXaBi5?f;?*sHh_3ZvudRln@b=?2@c7F#w(7^VdA9DYNZ=t&l zMSnf_zme@*>2cvbKjQwMYxg(M)51G`%>8d{_ur)ln%Lg*6Yjt8SLtq3yI=oP?*DnV z7trIv&-je{-^}hu>1pAWHgNx&+x^M(KnvSX{ha$R{9(Equ=_{pzVmG#o5Y;BaOVr| zf3Dr1L{AH^v61_Kf!%+C9=Oo<@?UcQh0mtD7uo$YzCz#E()K6mapBdz=Kf!7_n)Sx zh1c4|{lCQS&!-12g$Ma~ul)`BE`1ceN*$xe7uc%u|N(#GyzQ3wxQVf$sf;3?_5;nASoZ?*>>Y-{`Fd*Nx}-G7G1+S&a+ z`{AMXw%?qFI~{D#KM0QtU-v6Ke5Kvr@H^bs(e^ce!V|(j`wQ+~W%u9z8}9F9d*UB> zQutfP;E}8C{#zwVWq$q}xW@J;)#p#^p9`mi=c~_$)*ijq?vGcW_pCkG+4d>wbDA?h zUl#tf`rKvhvFq&qo9c6vwTHUczPvKr>1z93Rp4>q&z=SkhwT2e>Tuunwl6pxo)A7y zeU7r;AGe#`&sU#|tlfWu?GKy{PYR!S4m@(B-G8PIJkZ_t`Ssu_;V(CUM|;@)7aPHY zJ#F9I7@ihhs6PKVbNqYR{V$urLpRy}O$)elv+X~q4@A~;;&k&mWby@^(;I!`|6Bx5 z3%~smc=Q&#|9UHU;8xp9UJg$Rzv&9N+sE!NYYX@FwY^dYcwBg&j_^>}?(gUXPYb{1 zT6pv}yZ_8}@IapJe|Ci@h4=3Ucl+7>EjPk_{cR8RgvW*7aT7dryWRh)H#{x8Ss!?G zfZbmah6e`P-n1V)DSZ0vaCea1KRgib8*F>`A@I2H$JFOr>-$w`h}}Oj9G({5{VsTP zsNH}5Zg^mr?MFw!lfv(~7w+C+_rD$k_YJqb|5$ij_%9E_LwDN!?&@>3_4P^%Uo{aP zb?ttYhv9*{Y#%!Xo)n&V6z-0&`{zu9`|h@VTofJ`zI{48G}7*$Hv^s){^Trpbd=pc z@-#d!8vZh0hZH^oPYJ(r4(Hrs_g|&^Ho|9dPK{^L58i9{@1rMvh7V=`@p^t<$=iY{6=*RA}=f6w$-GEPCo5}eV3eXRYvHQd5iCy;m4bP+R+;8`D7r>KO;GNRu z`p$X*9vA(CboX<6ewRh)haZ49;p@6F^ypZ4ckch(EWVnaX8$ktFL?>`6Zx2PJN>km z;eiS8I^6D%SKz+M@LSm5_bS|-0&hd_y96E=zLB05-tjeV_Yr%}D!MDYX`J(gFQBJ| zH(1K~kJ|HR(Ou!EEaQCP->ra0r`vO$dmWyB+V(2y1KjoVM&KFSC(vEtf79c_N4~-N z!Vl5|bL{y8SE26;|Ct^aKIl!(7rvDqm}}4P`WEL4FS{P@TZ+E=nAgmu(?{^|GI&S& zZQpUu8o2pB<+(z5=u^0PT<_ckPYQ3noBhx1{>&fQ7ykDi_BWt!9;bKgg{SFeyPy9= z7mw46_QBnxz1`>bvrjkkPfNq2U)cS-4#4Alo;2J2_8>g)rQL7!8~eg%{?7hacK^U1 z?9~FUFjh)h&$3;Z=q!Msv zi{0Oq!@lr#rP$wU_n#`wzVP45u)odjUtNxU;d4%5e>?iqd3}E?50BB!ahOmM9@=U5 zJ5}PG6x`gtWqfddq3y$}aK7-Cr_p!Y{Q=cE=OEndPrL>^@jJXHKNq##>FoanA4q?& zCfs)vZr*?R^BM5uG254%2~X3f@c0a`1&=uRI%vGnS)5UMuKJth3vOVD>~*!}*O z!X18YyLtTn(29OG`sVrJyvyK0e%`wAoXg>{I_R6{&l}sYUl)EipEq^{;ptrX8+`pa zv@JY-A>6#5a1%X6zkttU(RQ5E8h!Kr!2TWJfy->aq$AuFUa=FmOFzQ>{Fv?rF~@v8 z?RO3OPCIyr{nFRMQ|;lm(7Seq`#ReGa2L+u=dGLmxDedW&s8^mS2uXLt3Btk8{rAz zWqWW=$nNjx36BV$brU=#Jg+xAc)dNR^{w!j@KgH2oo;sjt1vt)e0v_fyWJn!AD*Ds z%fSKt=XQADR`gHdaeia~JV{q44A#aP>BZ%w@|R z@X$!Rf75VyRQOhUQuypU(GT!*=goH8yKsk}FK>Jw-R0-V8=rj_`oRb7{i!|z9-$xO z@tIC{C)@p6ccUK{zL1_4-eV;D{2Y0+KgW#o^Wu%aFbaKVn!Vk+qv1i}&(R~otK5Tr zg04R9Cv%y5FFZ9H^UeF5TigeaJPSX>9g zLiEeB|Jwub@XK)X_+2;-o}iolD-Xhxuh?^5j=)ok;p*2KWG=6bheuzt`_JXWV{!Nm z?5~}0a<4EE?kq>YEBk{VI=L^Whh9fNpVznJB=l1&;lW1O&u%l|@o(X8a{e6SeB9l} z+ok19_Jw~z_kWK$VfM#AjebOUa4z>p_#gA&!Cm&8mGj{-;X@a|ogeIe<3;eWaQ%5C z`aD2Fxc+y&u7Yd+j-0K844GpZPi5`N{6@`2rpmzVIt}LijD; z!2Lhlb82mcM}+^g9i9^Y$+z&}K6}pOLU>I0jX%Jh{dWJ%J@Byb$M(V#!mm*8@6X(S zezE6#_6s~B{LzE(l<>j7!-HviPHpu*V?946{Jq0)=YZWGt=?Cv`(fd&oH7|t2;Z9n z_aC(9%qtC#2*0u%JSF_+^6=oV_MC;M!ehdx`ryuQ=$q%6omo6k1$~!&b6z@1PYK^k z5B-k$rXQ+`ISJv*>487&zV9^dzwmqMzCZ2$R(eEu+iIAT7G6LPAF}71QXT!I@FDcz zUv~dvdR%z@8kplhZ1*S9qr!L6oxkmV+tV?}6+Vxi5?-+;`k^ECoRRc|@UQ8Cf9!tX z49tlMkJ5cd?fzbRM0oczF()njEqeHvJ*QzU^pnD;(t{3ueqrucztH2tZ#WBc{3Y!E zQhHQ)t+Ua0a_s(Cx+{DOJth3o+L#k6Y0r6*o)BK<9P|UF?Ec;KnDFoDzS4F-oacwRr-j#PfjQx7_MGwbr0{L@ zV0F9SI)FKG;WOy|8g~D0dQ^Dt^D)Oc-R{3bcZHvxi++l3zMpb0fTwF>j`75WoPVb6 z5442)YT3T?Vz~b-+gn`<51b9Zj^EcDXbq2?16OZf%v{z><=3R*qB3IbcpFVrSI<*H)h6sNbe=EBfa5B;3#%9;ff& z-*``X38#jW__#twT>1g_6MZnJ9lgP2nB(gUUrv9Do)n&QIr`Cl=yzm)JUvCP#^=2q zrau6E_3OJcm&57=X6AYghWmJYnq9&D83M1z`90`C`eXF!LG%;D(BDblN{`U@VDUSH8~ zoW(jUTrYA)It1Nzm?i*b$^9*1fk5^xf{gghF z9v1z#vv`Ug7yTyJV7~N`bmty>e^zJla@S%`Nc00)ycazt`V+GF5_($nH)rvj&e)&e zz4rc}o5j1(qoO}Hi!Y|9M1NZrFMl2OCvcy=KbK|k;q-{;&(Gpt(UYQIMtwoik9X;p z(EVfV{kc1fFQL1l|5Fw}w=1@r5d9mn_&B=netUmj%Hm(q!=j&~eh{y(uk-*tF8ckl z_)NO5`CZgg*keEq_?EUM1MjSPtw!ucjD`WKj^`+*iYl_yJ5Rg;UnlN z;S1=2arT_g=@H?7(UZc@xdGetKWNYCLU)CarzeCjr~4xIoE`MA@Cr9#yK&)d=+1b1 z&VBTd@RjtK@bBnp;T6>zvol{$^X>UJ(WAm6^px-y=>eV?=)~LiAUz`d%pTZoQuq~g z|3rI#rqEsCtLO>gJLtZL>^Y@-V!L7C-RN=QQ|Qhld(NBmknmmfnD83Cu-&xq4)ox| z_I#Hf6+VZa626umm~77}qDO>RxrxVLcw4%Eialo_-4(vV%y|@j^>EHy8r_WffvNCW zeBEBUH@Ewk?O)N|$8C?RHy-Hwr|${d@4poupKg2mKJd^}wpZ>8PtUM@Gd()X_C;az z15ew2$8GTBGq(50gS&HWZ_*F$n`iqU^!RhOzuO=E(0topx*eV_uzkh=cyxj7;|9V5 z3vC}V2%cPIdz-;<_a)oQ4}tq$v3&dRl|a=UVUfAg=l``3&> zKf1>5-=|#n(+RtO)7|Jt5_W%ra@|k7XZOdCMBo3Z-TzFv?nl?z{iUP0|L@uTZ&N~L%aWza@`MnYWLUQgMR9LyZ?)F-FH5>`DcAkPw|2kWgXnjXedW3zEks{^JWA$rSp**X z!S-v%!()5k*Ox^9)O@(}GrR`aIi|dXbCxstl!_U_pE^!(0{X#yko-pY3s+3Weg;0V zkMpDSi12UdE|0T$9F>@YIU(`*YDA9-?@UjM`LpS9alieD9v1!wJt6K_=RJb`@eA)p zkBIwKgdPySfF2c(i}iF@JbtS@itQ$Z-%JmRIpgVR@wk449uxi*JtX`udVFn#%nfU9 zmqt^uKVjj$=sw|(&|UGkexL3a?mUM1N%6ebgdPyylb#aKt5cNg$3?Q*DVhCWfW=uy z_tybDkGrjN`ZV_Gv-!A~W4sCaQ`jH!IQnh_xOzBbF4dy&R7bdZ95s9b?liRfNqVR) z{ANCW3#X$W75!mP!u^fxIW?Ywr`yl6|wEyJn&vJlCH8=qz|rc$jv!3Ieo9+EsNKf1fe~(w-)EN4{ zv2gV=HFLR>?$VpmXDHXtw^2Ud@@22Hfc-f8>g}(YOXK;NKbLNf+j4qv0=B!T4Eh}k z(9flt{#Ls42>Qc$I}Usv{XJ9QrvDc`@&xe$_uyv!TlDnj@Ncm=Z5N}T+5#xS+wlW> zl5V!!&Hlvb?osrw;p_0`%g|5cpxKA_hj-|Ka&WW%*DXiCHr>qs zo*u0NnaJxqY6bce>84-tb$Fl_~ z8P5AjxmEBbbkl!{9&Qa;dKxx-!JFter#ImKtfl+AqW>Llug-6wUzKj=Z=?HqL7MRo z2EC2Gn+Ir66&pBAZ)2R>ec~PTVti!6&$H7vSfxiCeIPym_5kFYxEH z^h%t6DLp0pW_p^=-FF_Q2l#VX=%;oTQvyCLCU(8I!OswY_e^Kn;rA9_UiSM;dx z+!rw?CVVME#pfXeWFA0lISyyf6C&@1gJSZTG9b&pEfiTd-gD z1NtDi*`ML`^k93=R(g1-J*Vk9&KYLUd6J&K18%O@ZhGWSyMO72n3HmCpGpsou>D(l z>~7n0*JF+|%J$$#@bGBc10TZ^_rT5VK926Y&-PF0;W2P?yI1@abCUPNui$Z>tDZkk z_WqNld|r(_f@e1K{MK$Yo^PY{CcJXG9LmzK^&eiZr|mgy=)q^@!D-kZ^L@m<+b}0azkvNd+ugmYr__>;Epru*pX+uO{g#&KUqwD0HJ+4rwRe>wY|-ou;}{V=`2 z_#5{1`Y4O1>48<~oBQ+W>I<~q&j{VTkE=r#zl$Dy6LZY_xaQGQAHda@-OT0e4>11& zy7_)&K0Wp+`XBKDuwA)6K5;$|gm`^(*5P?0CZ0FiD%blT+KM?>@P+Az^h$Jd-XOgj zw`1%r^txwoe~@1p-8`+ez=HSh{0G3Sje{tZ347X6FZ@Al!z?>~c;>+2QW zgTA@{Y*Ma|k1Os!$Jmb)p>OU#L)K$Xaxc6R_kR^V^)vi+K3>XyjDBoC{2lr@`u}3@ zOW-3ps=Ei8Fz+7g55X@m%2!{b1FNT1**PQDE^1WBD`l_y)8A;RKBdzqa zcUS*@{obor)vu~vRlVAMl<+5W{Qo%q22M{;Bay|=bN%ln#B5h@Rp4LX_<>&%z1gmQ z3pn-{HwyJ0`vURbcZ}$t&pUv`uc#ik^78&2Z$F>n_{6V?9@1B`pUZ)dtRG+E{3DNi z6r=kN=ku)FNbV5<{|?6wJ<3i0+Anf@p5n$wzQpx^v>ShrgQqXPbv-|_E4z~9L62?4*C7@K6~wM+Ev8DDZ#gcytrl+4OrKSKxPXe4jx7 zkP}Ex5q}ED4-53$75E;GZx;OORSNtDj@JbG&nobHIUW)G7pXeFHf0&Oe{+{Fep6JH!#w}VrJZ|D8RB^-}jLby5Ke;3CmE+zbbdAofx z_-$Fg|2F2&PmOHSxq_VjAzz1n;e@jyXa0`(a5!=c&__;qV&vv;yYUlGemLM~L{9#y z8~+W^pK!vaNdIMS`~ila7MT+8_X2)8j03kqzI^<7I{Pyc`4Go9Ltae$F~^@@WjH5t z{PhFCH#>ee|M_SrmkfV~0-pf<$>z(BzrT7U5;+gXrMJF;%GJ-G?YtZH_x|YLljAdh zpXMcxJo)>P$PJNGA}2qd>SYtB*#)?a|JM}wZx#4Epkc`5zVQcg{6PXCublH))4 zu^iv~Q#pRp&*k_({6dbOd9NJ5;g@p!{9nuQkNien-cK<6F_F#Rfp!f)ujSv}FBJ6W z{Z`KBA_e|Z1^yug{v!o`&hO-McPj7~DDZz*;NMi>gU94@U!uT2t-!ya!0%AtPrgqs z_xTF^wF>-y6!^CkcohN>vhVJ{0GEA7KU3h3c|cD8E(LzdgXCu-ZzMmnnYG49ao%zDdAOfmts4 z&zgY$CC3j7`g|I2nf*_Bh#VhM;JN~TuLA!r!=eBE70qa_=XQJK2`Cr*17CkJ@fgE7 zk>juDEARycew_lpNrC@BfuDAw+&p6wD zp-*r;%JB!d{lBf?^E(Cpn1{>te})22EAUwb{&od^w*udMvRt44P~eeA$m#b!lKjbL z!Ed|~aG5`RH{h~%@@)nFvq#D4AAX7)e~bdZ1_FKPHxBhv{X#lE_VYf5^U8Mob(;cz z%Bga>mn!gU6!@2r@IK3n1b8T{GK@ectmR!@=*teH8ff8R||V zr<_UUJq3j78I{y7EyD+NAu zj=a3O0>55?-vRh(u>NxM$@F3IFihl(C&~FgA8=WH%_!(^QsB2K@Egvy6WY(`07t)l^tH4gL%VAtk-4W5eMF!?065au-s+|wc^c7g z7U-`69O?JHk?61CP2=fLC;I&Y{a*o&^qb%0roWHVU-oc!``7-M=nrxFFL3@}0vx}W zMS=dL^ND`{wQl}z1{~=R2=rg%^aml24*r<>=bu6J*9!Vy3pnZ@eKXPT<@!XQN%U6> z{O15i`ibk^{7-ro=P%Gd2XLg{FWCRPoIWb(bKakD{sR5i0LSv)DDZ#$vx$DwKa)PD zJ-+}r(%&P{KV}Qj?-Tfc25_W5Q_$y8e@gVb1^WL69O=&&=pPX!`a=f)L%LMu} zHKN~t1JT2_TlVukz>$8FK(ED!eggK};*Z(>e*-wu9}~)}#fkn1x6fVdtw`j1fFpfP zpg%W3^t%QAUj`iMZx!e_C5e91+ex3Va{h+_NBW}z{o_(Ze^j8q4REACDA1qUNA!o^ zLHx~e%iVw@eZOFz&HY4w0Pcs-||6ZH8ar;qF>decvy^B2VbYJvY0;K+ZI!2bqLzfa)*bxt1@ z=+E0q{3itZX~2>HR!(oW^ADKxfx{9g<>en*RfKA+_D2L<{whKPQ*z<&yG)aQ^u{|-)nvcUfyPJcwu|Iddxe}R4x zaO8hf=%+rz>1zW032CC={}!^R*$+-IoPAoZKk5qng9`i}1^!1Pr2h^rmIfnNbQ z+TpNZhZ`9^jDNTtp32Mh`zM~r>gy2PCxAa^aQv*ZPK;pO;IehvQoX{Q9R5eN@2jek$Qx;k*L=yq>r7#itRzS)ezb&gFiF=+EZ; z-ETR*Td+d~4wf>%C-C`iI6#W{!M~>RzKW(Rk$3$G;SsnG0e>!Ie?}tXTL|CB`J}j> zuliHMYl7T8QO-w@dq2lF{XNP3A=m%48qptmGU1CHe_D*o-ADL-j{lV5Tsg;IXT!x( zvUYWe0)Ie(KXZqi&)@DPeI|yfU44|>p|D8!;S1dOCmHTgkpFXGiRkx_5it%3&ku9_Mjr1q z@mmkd`JDeTIX#V_E5K#-68)weuL3T! z&zltZ3%*5qo-gQs)g6RiE#NPMeMs0JMTBy__z2-g1^o4Q6MoO{+;(`?_X+R6&yBzQ z2ZYxI`j_5A_>BVpH~*OMIf2i+eoFX+&~N_7&k0`?_}p@o@Th=)`Im&>D)2w+*M#p7 z=+FKw;g<<^d%`ioj|hCuxu5U@0{!U^5?&MVNB@EFg94vN!frOKm#9$QlTRRgx1i_u zP9%Jj!2jzHB|IYV`P#z?zek||?js1_D&Y4$ituX%Js)-|;WrEQ`-n}1?-%s^#%Y9~ zEb#g9V+lVj$os-PQ3Htx? zJi>1m@cW-a_-296eNQ9&fFSp`e@ysR0e|*02|rV)uRr}0!mk$SpT33g%LIH&l<>m> zJ{=?ckf7&j3Bnfz{3R*EHw$(?0_(fjKKBXy<9|l@$pZb~J(uun1^lajLHI!dk8dOV zh`|5l!-U@|(EoIV@cn}R(;32V7w}K-Bz!{PbH*6q*9vl92-dhFyXRvOH_{~Cl z$X`tOJp%sKO9{VPz<=?Vgzpga9DhFHn+3Ta|7*g}7x-MD6TVyE^PhRbj|k;`;tb(u z3iJzegwF}|U%s61%LKWPEfan~!1q=Ozfs`-l`9B8CY0;kCgC-K&r22v?-%s>^&;Ur zgzx2|7jSxk|FIVl9uf4J-$(diLGIW8p705QPyD5X9~AKGUPkynLC*(YPWVwl?gcQx zVSZk~uYV2U=L_YEypHfqf}WTD1L1RmKJR)1;adgzCtS<<2>j=+wze+*>J_Y`~_alFZgT0>Y0LvWg=S3Vp#Mi}4``ilq=2_f;uLr$_(?7s) zZbZjlfAkT#{yP=;)e8Jm3j8Mu{GlHuJ@@f-oL6!^&*%7l$VY-d7ja#$VL1QA@z+-r z^cQ?gUal819OAB=KaBX<&jG-3y<@8oPkY=ggzpgWs{m*90(naC$JFze3jD7>PV`a8 zQ{trmoB~fBBKrLT{o54y>7O9_qXPZkDDWS1d;;>IIQ7YWlK6=DS2=!Apih5_=tcYk z9Nz?aQ=I&t`d>sZ;;-j;O`t#N(?l=g3miWz&>vIaSN=Egi9%i$r#?Sb;D7xYxqp5W z;8^ebIiD+de95?#@U8D5{!ilV`A)!Pe*U!2%JD;hBmYg1_rJz)fzsFH_*Z=Xm7(Zu*7Wh>wW>g5$dd`r;RfUc|r0 z@k0Xr?3ajM#DBu^&HRAA`8`e_CVCP7Cdc;)^gF&x^dkO2jvo=|pYau<7x91O_|^}) z?en<*A$k#iHOCJK^pE-~(Tn)YIUc#$&HpzFT>l#J+0E(Ae*Ay}zl-Dh_&S1V&#A8y z9})j7$B**$6qA1IH;7)uKgjWkkGS=J_U%M3;{U?&g980yzDe{V{sN9~`ly@#JqrBd zZxNrGK>tMrzWv)oe^{XZfC7Ko9Yi1fm|LIMDe&KOe7`_nyp#Bd_}4goRG{Df9ikWU z4{?0r7Pmgnx{K&V{CbWb6zDe{A$k#iF~>K3+|B=&3jC(;5}(b7i2ezDpTpM~PCm`~ z=Sg=Xz4vFQ0&g-L_Dy}B_os6_{73@lCHJ{2Gog&J%s))r6b<$jn}{&;I>{C;0dBR>0Z*Lm@xXH#t5h z;HNIi`-?vZ9PNDg4aEN;+&&Kij`ec%TEbt#{pZk?Nbmj0EAW>v9FjEsEAhFCzdHCr zlDqjGgu`|S_ETlJm;Zf#L*+dpw5#mj5`K?>KgjXJ0zS2m=x-PBV;tWq*ewBxzR}JT z95>s41#tYnZol5$o*&@!hXnp(e@}dF74UmFeo(;sUrh8j3;0!lqufn`+>2jA^!o++ zw{!ej0sjr)*iX#~-}|4ul=x5li@RJWUPbtsA9Lf^znt(L|K!GR=lFrQxberng6QY| z*^Q5JJbJ*5zmns}1pL1_e&_}_{i9w<{I7ni8z1HPnQwFBui^L+0YAd=8~@c!|KwK@ z|NeKl@xSKyt%84h8^`y*(@p;~j$ig~Zan^K;=lRd-S~?*evg2End1ll!%ctYYlzR{ zf4cEYIKK5=Zv0w~pZsn&emBPt3;45MOZ=}r=%!!d`0n?(@tZh){(If{y&OL(;Hmw@ z|K|6(>E}7V?a z0$%tG;WrETAAgSU0|I{R3xr=S;Gg~y;WYt&h@MFUIX8j1^M+7|fJ;HAl@CW{v@Ph*W@p}lrR=}V66T{|;3;0Km5q`3O8xIhERQT@x=nsV7F5q8}JdAy(hXnlU6A8ajz@PIl z!uJdKcTXmKQNUmGD8hFO_|U0@?-1~-Vu-`JC&6Dm$nix1zavicy9InaLHL}2Pb3MS z5bz&z{4xRmd5Y+_3ixav;X4F;te^1B0{&@^pD*C&K8NTJKGZ!AeFMjD7Vt;>8PP`t zK7YgUegV%sm*@`&eEy5$HwyT3FChA@0{#h(?-1~zzu@`^_@_ADFW?^?B>K$){-@gr zKVQHPaD0=1pD{%AXA1bMI35x3Q-_KEWC4E-$Bzo<6i!bQ{V@T*hT}&B{LB%ezem8Y z>zxTP>(lo{7eDAF+=nb0pD^V;U^1td?(>Y1v`9%=YW8JiQ_j4_^#`Se!qZ!h2z%>_&ea7t!#YQ2j^*J zxB)oovrpjjZH`|p;N!5*gTQW!e&uYk+=e@21-P=WtkfuHp+a{d=7@M{$K ztqS}^*cT$x|2Yc$l?wbP|1RfKf0rCTxJ;iDVE=)PzNo;jQsCbQT*m*Y@0Ig^8Q?Pdk16QSh5ZUL z`ilXV>Hk5%v7K+?_lmxp*UM93zk-bZRSNvO3OxIMIiGJT@JnGIfK2Y!0GE~PF|e*L zqyHS>GWv(XI=zhkVg8Vw z9*2EMGJHvazgL0(RDqxW13CZaEAU$s_$fc+^Nuf3c{lU&{w>G%3;0_AmzC>x3jDeE z$m@4Ofxi!M8UJVhNRE#IF8h98p`ibP0zVb@fyv~i0hjTAfr9?wKaukpSKu#D;D7Z~ zIiJ@m@NX&br~FLL=L!XWy8_?#b2*>?P~hkOLQcOAaG5!r$0r0lvZYvQ8ueOaOQqU0 zwhV5|JiDpS^514F^IN9pi>1P|ivdO?)q>vCBU_BQ+)Q0B8@agxP@2C*wls}Jpek01@aGKt z71_dGgFm)3j67jEy(-%{u*y{*L5w`g9RqosHz{%|>&6W@bwsz^PqBxzS8^ zDwm5CjJh#fY=D__rl>|l0_AdrYHqevoz_daLbF{?FO{bC{GMGGm$LC(8nA)fF3@M8y2nUQ?#wm^a)Xtn9NHe9hoFuY z1%1kTMV~e5=}D(Ppxtl|jMJanMUQjX{&lp{vh++1GRv;UW-C_GRZBv3ntD;qMeUk{ zdP=9YXv1g@8#DTRshM8D0`D}LnN_UdmmZikn&UOJQ~L5{y{IMN&0(XFuNPPRItji+ zFU=c*bkwbAYq7><#(I;Pi_NgwTq-dUgyRdT)3 znO3e>xMG&GkhR;^ABC2x)nRnvw0(IR{K(WLR;QzBj;9&nn>M+p(=O*DQ^cXU{PSO} zE)D@*Q77pzO{OwaO_$r2(KLsmfGgnWD3pRJ`f;kEON)i(oZvZRHPJ@P)(o|zqd5#! zYfH)|(TBq-y^ceVv1~M_^=~&SM!lGKyzg!`ZCN{vk?xec=@nLBh!l;_#`F34dflkx zp_3cS<_5A&_+(}$jhS?|Z+W9A)9^ly=qz8LX{;x043+doBdOPFC8#N=2ALfkcJ(x} zBo0vEXJK+G2V?b0qlt4994J3;bf9PsQe9uhazvXZS(aQaYLtsj8tS3sY_l(y%P%f! zniiYZ8^wGMW)M(EnLG?Bwfvk8vx2%_Y&J6abXJEy(lFTGm1Cu;&*z(&iJgEgOy#ou zFbIuBXR7tRdcBY!)M#zT&Wh5SN856=e!UBY5Qa`NGqkrQXOeC9=O(A&OEwJnGM4?xr?bsOO)u6n zJG03gOrA4iiE^n}PvIOpGYn7rXZXB4Gcqv-o}h2IsF&blOtarKZP#dKa;OTSoW&;d zh~QhIxvYUCS_9_gnH-8J8I48;B%q@~C=FBE9JrB&k)D7z;${(Ym1@CY6t-v4`b$;4 zFjy%})T?>mgAyl6O}BXFF0}Ez6>xInHRy`aRrK$w7UoOFcn!4M0v&IYJ87eWF&FSQ z=(4qb`gKUJR4c{2UNZ3{;e#^}>fpqw`h4ZWVv`iGxo;^|;XS=n)EmH2i$V~jHadFA zt_)XIMVZiY;Fn=epJy==FmCZmHp9@+D4S*cqe7gw)eirS?9qp2N-qC)Y*(Oto3}r8!8rrdCU?7GDfK~-j7#6QV zeW-hIj1t`T>O*r4W~Ub1Fsu1h)OWsGDu6PpW#g{e?lngzUo*BK0Vf3xLEaTgX6}`UH zU8YH}Xb~MkciARJ*N7dp{!*CW4nV6L zhA-BrC-B3Cz5v^$^&YgVj$Rlb-;7Pz<=OCyhiQ=+9;VTIa%wO5;1mo`^Li5lw^4EA zEy$$K0r=pku@8GS_&1CxU8*XJ*y?S~(_FkIcD)b7?P>|T#9@dsiz-0KH?=^++X8ft z1?Z>jQMEh%?P4kvNfR4e8=b`0h3{&aZ@WJ9<-OaY*|aLJs_}T*-U^sNYlM!2k95pt z$OcBt|3-%!HU1C>r0zb81_mBF7_NzQxPGC<%<)yO4g;1EKCbD{R7Oh0*|{c&nXObC zFg!>vzywW$F$P=B$jwwTFvZhyC}=beGd*nJxoV-2W4%gFZ#L_EE~mpMo*S<SCP zR2^n7)pAbHSO8Cb*M3zt@*LAU0rX~oKnQP0llHHalHP@nFsWD+itIVWX~7NKcV z(54%;Y6Ic`D|rXTc=h7Ul6M$j*unJ1(xNsmemLFHuA~#l&g*L`(-WnN-fmYF6A16>&iqp;6jZ7le)hTO9Xqna0 z0=Ntac2)`vj0U1hw?`Ii5wP##3MYg%%qSsR3Dq5kcx*ODqZ5p-LeD!u5y@O}CUaS( z*{DPGG*N8eNFOZTEJ6q)GX(+IzFa!F!2Bd@t89!gX*6&vO*+xU4OHV(x$*1}jA&xH zWOmnRHID+J`;@J>aF^?vnYQdJQG#st;huc(l6e{_+p}ZwCpR67nk{MpLp4xzoYt$| zx{_W}XE+2&4e1T&!j!LhyjHsx6mpymNhwV6Zr{krls3E^CELECkc7+9%}Kp7YmCp( z(%W*RF6(WT2HNPZEcIN=65Y{jDwzY8O!!u6Fs^|QS6NA(7NITcb%hSHxxD0SVsqmT zzb02~%{c(-pER2D^-7B}DwGtwz0yRA*E=jDn@_K{jHYK->gcJ)wr?m(Wm&Uj|3HBu zd(0fupqsZx1taxp+1uIo+w|Tp%r&TR_wS0BO{*D^qHto3cc67#bImwHQe4*7bZ=}- z^L-{~j+8o72*)M+l zUhn}kuYF;>jOyczUsT^648ZA=9c7~J7-nPC5*N+G;Eu0>JMHB0(`r(eNgXri6$Bdw zaN5HLLijyuQ5JjPzk5dVP%G90|6af(+dl+xloPX)AnLm^4%K3j-XFfhGsqHgH85rzjO*!(%B z@x`cZOHCsW8)znh2G5dUixvHZYRWGgvx{Lak2yMdNl_pzc5$tt!xWCiq}oerNv@KS z7_RQEEJsS47gTamlk=5PSX{1v^*bz2+gDVwl01-iX%QmqdpigiYVF=oNHW_xE-z49 zYTpZ~T35>uU)#!G*4v8GX#Lqz5i5u6Hn;#=#M%n(Mu-J)|c5f&onIlBl#ZZRxcZThMcEI^czLUM}$iX#FQ8g6V=vXrSWlNXNK?Jxu z3X^$*Ny;9004DjkIM8DL>+yIUywlo<9lCz^b};u&1lP8|(du5KK2@b1ilfzPtzqt0 zfXJZiR3&Wbtg^FhdMPsvp=>tva}3AQu&n^XfGL=ALc|SE)=uel7)Zu6y-*llf{lf^ zL9(Mh`E ze6WFhWAO%JdGzxaq9Nr4TQ9U2RV$)hp&0$H4Q%BVD|Q0=>nGAvK? zNkR(7rF)D5e}*w$d)tNvHHbr59G{8>(o*Jloi!$?9li?~V-?9=^H9rBw0JHFf!F3- z3V;!|ofhNK&J82{j3y4{{WBn*hQvbf6FqM0X=b3`EQppY>&^TeY#yF8_Ja1X$HkiJ zSPho7_XJ-7;Hx#*;lWZr^nvDAFac2mqh8^B>ddw~-QtxkJ%%gk!$!@h6ksujf6aKl zfm&OVBe>QM`+b)f@8kLNRi4luhg}PHqxLLSl+r2qvWl~nbh#wD{f>7LDh$6+2G1r` zxaJ7Vb<*UV!GCOFaae8<+ZFh*ayt(;*usOcS*aMPSTLJf6q=g3q0GD2L@XYUl(Z-s zd?al3gu*Z25C&k0kACU+OG2*=r)1EV<3FrOM&Ec1*4&G5SSbrWsbp+}o^=m;)D-PG z*EH(&Myhr6N9Y;0RSQe$%f+f^Us+eU0cW1j9(nE1Pnc&*Iwj|4K-}c5-8qYa zKs{-uI|qQdbGfQ3WyVkXxHXF<#Cek0vgE>B&`bGy+`pWymrTwrWiF4j)>8A*$@vX9 zc967TDJ7M2O%6j=lcn)m8;%|_Ik&`i*e%+YnMft))pA&oou6jI3acFgu;O;8W+vBc zz%e&#V>ck}txyHoccj&MlScjGdQG$}bidqC>5< zRFg}&YjL)A(VC}7BkRNb$~12w0xb|MnyK%Af4#J7P!KC7SUSV+rPl3&K9EHWkt8 z4XKQH(i#nU_XxKZ*4e9asZL`oTW*IhOC>wDX|-z>a#96X$2R2FR?`M#q*AW2n0q-OZ7VuvQBQuk1Da;J+i_n}%}hy0aR zu2=eo^y83HDnD&Gb<6Km`buZ6T>h%lR4rV&^kun=RglX3rku3php3K$$(ebkd~!1H z98FHwY8B>2IN3e1_VGbfEYNeKHdLiYco>fZ8#R;RF$U6~K(5-ADC}q|@1R;H` zMMyzP4^B}sgp`uDj2Ow^P{n}c)bhBFlop0Db?Zc2YtN{8qK|Tp;_}_FoXi_=mD05c zA4};qF{~-4YUx_kkx0%A5)NyL0%f&@^gftid3o!xrt^YhJG35a2Jq8q$Otn5Cx67{ z4?DjE$d1leidW1VxmptzwrTk>!PhkTN)+U4@@NQa{BZsmayMb(JJ?pR3$hi!CV(m? zErMLj&CG>xh)OI+yt95G4QoD`>>x0PpM|l3Tn@5&v&40f)u2+XL;5%C0C8pl(^SFX zru1+P4lvYqV^dWGJ~-iz0f`wOS6@L}0C!#WM_BqXmg6#tLh+ zIGTIdyH-o#9hdV>7d*(1Yx%CAKNS=*eXOw#wbi6`tyZ3FjJ#IMA`Fk$YB40^ck_c% zhRfFICnejxW0uvNM_8dvYaYSI<7>`+Xlg&H2&$~ZN>xrj*JxQ|&MBlAgscq12jh0IYv#!xnC}YcOv^<`DVr#S#>6j;BCkf30S~E_oge$S6az97F_F9eC zQI}yw7LhLAt|fEG3$PMfcsGu(Av;KvUx^K3E%qa=A#1cO#)|BsJR-7|tfDT&N^GKe zHZQFqTS$~&nGM7Pe{0GH9_3eL1IZ@bwPXdE`YW=7xD{zF8Ns9biYy>(KVC})5LIX8 zSEthF>5W;+2grnCCZK0`EI2d1(e5YV4i@puym}czv5C@~qr!Vli+B}9+w2sS2?aMx z!Hz9h35E?^?8XNM^oBVUt1_XO!<*Sbws0#`jZJSH>2thc37GECjRh>Fck84g>9L&` zm)f_(A*fT>?3dk^gACsAUyv2rsbOH9(Pd zwjK70$FnokvmX{%K?)~XP0kGZS}`oKRa`wxkb>|ACdTE>I%RpnkI z_E0_%#7FIyXBY$Z=olY+qwWm#j)563)tPFtrTa`YVV=$E4qKX05U`~c|6tOE-AbgV z=b$bmz_IrI2-|+3ztZr+0HCmZD`ah=e`DAc(|U<#V+oe-#$zwYwobAK!l4_+M;*_= zSCprV=-n2FiHM-(S}+sPYQl>Ioo#YululvlGcDYD)`50PI4jM9XAO(w8!CU2;4ZWVUOXBfwzp_nNH4%-dSF5`MlKoY^1!T zj!a7Facoj5aU+wIsHHJXhiDnYa*HxvEuEqC1=U7O8F71$D0|E60%cU{3t(+gQtusqls{QdFh^^d`K7mm#Sm zouL`T^E8mDaF|Zk@X=|MQas|&75au;|0pEDWXUpN{4B{HhYYnu36p|C_OvQX2w>1b8Ndzo*B0%MV+K@?uiD>ciGIs zn^+HTT7nB!ft>3P!)-o62BldJI=mB+=F2P-9sP>M5hwXAI|pS?DVsmKwO`%rQE7N; zOLn!fshL@C9;2fPJXg(jU;!u!TjyI%`MW<&TwdKNM<&Nf-L0nl0o0vrBq*zy?nY&n z?E#d-Lz%AY&;zWd`~kG0mfqW(pIbwFbC!Ob)ZJ>zA3)u$U}_LV#fjZxD>W4pj{wvh z)spU^VLa%up?Egkzj30sq;5y+&p>f)mnRAXKljMDw_1#qe%bT62}y8e-%{7d1TfR` zHZqSCU#D7Zy$Et{yJm(s3yZS#p#io&6vJrWGUqU>=>^y~;Jo41nsIe+1#Aaj0o$I- z?!f<+U&7VQQbH=>sMhCp->~`;vIFlmm6RSrmSZOnFX5SgO4b9tiqMQ5r_K4B02YsG;B4(zSPCOcE)u%jU;j+#NNG1NgDL3J~!FqEpR1N z&9c?XKq#gOS0SaD6zd`3m?$)5l;)8xmxp7VkW^8brtRWbC|2qLtyBh6C$9^|Qemp2 zGLdKLG!)B(poP*Po{cl%SR@2BR3=d_oQGnW5VcVmNZ2b7idh2JL1_%nhOv^$*b4W&tB+gtosM>c~CM-!!CB)fUSu}dh5D9s}5T?)q}fh(ah zhjMppD0Wfq84E|77JJf;Lxq%0$hkXC+=&;Cox;^fX{Z)k!Najrs0yjf)X_f8Q0&zs znkfzEZ1(DHs^OTfS9DVuPnu386jOzyiP|ud^CBS`CKOFnhEeU;4#hSt_k$mg8Yyc{ zc}gi1D~Z>Jd6lqNT~pSD=5T0kxfhy8rm77cHs2R3dTAE#A>5L$)o>Q8xLQ8j?61~v zQ@>uyOvCvtaSsVxJTGKh)26CRrU37$0y_K+$G+7`-e7Bc7z$}oxH_b)SM*t<4kyx@ z1G&M=c+ZM&W<{;`9HHsqFAn#w3O4l+aPJ2c=3qlG!I=2 zKc<(B^ulU6X&T%yKUklgFB_F+7uY3$-LTQf*Nd$BSI;pPWgKfpr2rZZ7xj{nZ#HmI zA9fA!Unh;(Vxw7KV&^o(jZ{v{DVedlkn?NNPWhF2aN27Xb;*=&B}9Iep0ikrONE>y z-cJfz4nHrxvNotG&Y*kGzB%=vDq4lP2J)q9#XuJXZHw>O7#+Q2m+6ppQdMe5acNzz z!6zqvOfjwyyu}z4E6k2c5r}a{{2_&;=@(=c@Fm|5Hd1j352O1#iNn<9RO~D38I7eo+J#0Yk zIt^63H6C*7ps<(J*?9eeXg~A~VkuP;6wb%9XH1mg9U*xKC#yfQCiX$WYe5y3ozo#d zNM**T8v%1>2Am}uG#?H2gkaD zN}<7wST_oiu`DQ#!@k^1u~NttA=xkWKz%Gl4olF|#9?0*Z>PhIO{s#>)QhE9AJ7|% zMj@x?^G2glt>@tK!C8YP4Tg>{O07ws2k1sdZjVERME@DJZl=H6ZkPc zti>~@s7r#K?ZqTb>8V_H4F7{P4cTTa1tlz0%e=(a9eBB>J_`-D(rDJ|)!LRQHU(xx zUfjf9y|f4WV%Q;4t?$+A1ymkQ3KAADBeO#X#X((|9-twB^ip6pUN69K-6K<$Pz(4o zT^u63XGuF~O^Nz^WxQhBYJm>uqLtVIg)j=OaO<)LAn#qJl+vzMN=f}{N9|AL@K<7a zn9VK?Rm;=GiedUg_GN0ZnfXc{r>$@qFvLE*;HZ3iRC>exkbtqtDgviT-VSv+O?>vGBb zkYg6gGf`GIc$_hN$y-rIk*c3tz*MEh_^#$R&Pei zda+_1ry3s_$-=OIYH-_V8Ypt5;&h(qAWV*Qx!E!du-Kc(qF5<5BRF{~!lS8OkO-7m zXQp<+ZQ^wn+BNF%0ONjB`gF;NbO_m}a@krgU!AWsVS0v_xMW5hQz-EGkh5q{u2wA- z^GoR%j0kZNpkOqb_39FZycO#r3zIux$pmfz&o3@&nid1zMYIk^i86V3Nz2da^<1;A z7n_Yt9xo`z5hnzYaOf1vm*TLjf_IjWPDf!fGnh-&^g3KuPS=>H`-fo4I<4pT?1JP! zaHlwQugV1zc#bCN8AV$zq`-+-nQN4QZV!v2}PYPV4b&NOLWLIog`V%JOv%%?q-d$ia8&Ysv>Ea8Xp# z@XzJjIyBE6Z{%mRXm)6%+d9V}Gon4Dt2OH|?yQycJnXbs!FUXYWtdX1^`MYXk*6+! zUU0bsN*bAkp){YT?=%OTS`y-y5D{go8AH|D671*c%U8=V?ljUknuRe$|8}Ed)Qfos zB~CQJvSE~fru2oM<%1s1q!FqzzAXk&MQL2&Wi4Mgf)%4=x?YE+CR}Gq(Sp2Z5dikPnM(&5T4{o~ehJdC zf*#NSQKz|jbuXB=kTVH$d6=xh1}>Zm!)!7?ww;YKz8nq$*;)imXX}}fphoq| zIpa9rWanI*)7WZXW*Alus-*?oZ37mQO&r)I6|8MZ7hAwfM&~QVE9Q+HxM#kKYE59^ zX`*1v=&&5h8%GAL3v&_gattJka?NzK6x)_DTGb1@R{<4cavdSp{8SF7Hw!oqgk2b{ zI#_Yr@Z?iX#~u!BE^PUC7-s5_<3?Cq63d&-wJdCk3d_Gu>3zH1X&#+So>tvJ6heBe zi72#`Hxj5%|GwFF^TJhEh(th-Rfa^v5GtaRsqU&PLm{NcDny}W2$j-q7QgBWkqGFq z%8+OnLPc~Em{@gXD1`J_g($QPp;B7MTvlBj9`-#}5gz?QsDgGOq*YgfL_m*KgG9qH zDxytPZ1ol470_!H;ngsNifA3^TXi*f*!Nf^c=VH3K~GGSDP`+$qh`lsC6naJUKVX* zQ)TguA&mu3Tr+421e8m~x-3}G`VH??pf$aGq}t{k=^A3|cf5nJ*7Wi*LhES_c({v^kO?!Qrq!lS;R3GtKm@^R~UJ?I5Wco%qa>iw+c7IZU)x=TFcRrOb)Sw zE&WwNKqoBlgd;Jyv{-1)2^=D@6q+xyqp|GcO&Ik$o>YTqxEXWhrAfjQh$>2vjUZYI(tVGzJ^pY=0po;tCK65v@L$}#Ecse8 zTZXXXC2)wcT4yIsFHdW6gsOSiTfZHGlxYZ@mnV7be7Rdy&~xY60jTt3xy-ClTVg>_ zh{f_CXsJ32n_gJ)Cw9V?BG?pX+Qz%uu#)AOju$A%ze-HfKa0`YS{2TFfH5rVnHzBU zMiQ_#-+qG|Nv!oAtESmz9VsnVr85lQM;n275FXD23lwIxcBl$p#WBsw6j_NSX~T?N zPTW+aENS-y2!-!bt8jfk-8KogICgz~>BU-oFgs;82qy|!@I7&`=c{DkHvwDF40e69 zlWy3-QHzhwm#1Mp#KoAlvk8wJZ&`ZqO|Gn47Ra5CoEziZx+mF|Ic7h`#6{_lsKJG7 z^&;-kb88AsoxLYMwmFJVT&4FsRBG*f5M#$P<%+nT*;=?@mkRDDPBHOGb}&jV$NkV2 z+hE@Z9CPQbfF7B)jk_K?M7eNYue|BFA2};B0TnrHz>VxuS;A9WnDD?Q zO?>7Qr{Ts$Q&x=imu9(~k8D9s)MdT|?-S+Bgr_z?;XzB5&MI-~!5{GDB0NCOGe5wv z2>N5k;c|9Tn?nw|zA@NCiU)?7o5#U%ED1;0OG^%#p;7ALc@6}2oSciXU(@=i6V6m% ztB_I0*ZCj>nW7>Nj(vKez$m;+QVA3HEWY8rn3k3T!$bv&;aw23$k7+X5~QKzj46$l zP*Fx0Cqmc#3}S(tR*1tvJ2u~NoY=EO)A4(5FSC|eg~hm>A-z(q6k%-^_6&OsN4gAU z(}D(U=pT>KW86+gm86&8BZAEm)kYDAXwFJtZ^V-J8|;WFGe<+pT(e1c!P@t>IxJ}B zbyz$GcPo3rERZv|2)4TdjX7fp&jYhzS=ORbIdmM=myG(}V#6>GGU1^nK{%^4-u`%{ zxpT)jS|X$61#={G5IC~EOz|j{e>juPS8E0gR&lO^i{#Xp9bEakU5vkhSH_OaH^Bak z@8#ubwreFy7_8_Uc1z~3#(86)4WwCU4SIdjU>k-oQHn!9cBIWYN-s6iII4vqd&z*^ z?&ig@PLkwa*idUAnQLgt$9O|Aa2B|$2KxN*e15*p4sSO`N;)JGzy~p^rya!tq(mXr z`R;J7A0{Vk7NwVlAPK>26?zNV68eFu^HkMDa|8?;7$x z8Ell~X_h9eVIEG7_}PEPFXq%wiX)ARMadOlGXEmo{7RQG^K&hG(N zOGRe*J1rM6;n=Fee&mnYnPF!1qg}^q17}P zb-5F7bx;hgRWY!Nm%GuF?GUWPZaX;SL_GyoXljC;D$=4XAi=i3;kGWTo+k(6T5e~4 zaxez+a2uA;Dj@5F5 zx*SE2DTpZ|u&H8@1IorqsKLgJ0Bv&tyl0a;Wsc@jc8NtIaCzV1$4GFxALz z@xE2qZ8`F&5UZ&sEn1i_*H|aTlM6WdHS7#%1cU^Fv>&H39lh74 z<`asPa&n4hQ!YH`N5V`SOAIy|MtQolG+3!rVITwJ-%D}aSx-+-Gefeep!)n*&b!Ql zZcV9Pxp6PDy~`Ii<2rV1;xF#yE#9BAkHZ;Y#)LiSOEHAcx7Md&oXh%~bUK9z z>nerJ1oql6CRhNMW1iFOFIF3De|7>#lIBMSI=OIaDD31bj9nM;Y^}2c(_H-<^cc$x z@Pi@U9L+9q#k^MVA`Z?Wq*0uOMUS0)PqOXxDY(TB(P5)U$BSMaQ)IvvO*}cwmOEfe zIh>qrW-eq;(hJTLzF?IK3`g~nm7^##0c5PN8?KGBQ3LGjj#^6^MrF1+=UUUS^$|Kp zrk>T6%K9PakV&pF96o`@(N<&VyRa`aH3mzY7q1WmF)yl_M(0T(TM6bBCkZ7}&u=BNi~XDP!D|eZbDgNgAI(j1m%PK~;Mx z1{g&KbB7jZiWEWjQpFh+L_;Bo){bntDArh;aRZBm!5p}eFTx>qmk~%lx)iTe`f@PJ zs#986F9{96w!z{YPPc)zU4x>IXctfXz3qs+tIf+cQ|# zj5SDFiS0DScdX#TUlwu&R<7qW~6PG@YJyCL0FNnj9>lV<0yb%IO~{hS|QI zDBq(xeQVqn-sPsp}=hTl%$W2#SmZ= z&*nogzMUz~vd3BsQb4dV6^2<@g)#p0nmJmdw%k{s4hQ<<+%!u&VXrD<`OKt#l7lq% zP}{MA3EzpqG7$xDjDnR@4x_uNQnk8gzUCascn2q}2HEg0VZEHiSQm>6=IlhJU|@08 z!g|-zEjbPGBZw+vZH(9WAe=^}0()>r{Z+PE0rG;IW@pJSpbhX56RKsiF+)3442yqEa~a4RG6q58#z?(d9)%Ym?ROmS ztJf0rKHHSR@;w5^c~Z_tEmnaSAps*>49LKjhYRfZ{{sGzX8)vrK?+B!be5$x=)UX4 znWao+0oPe8kO-RwwQQK_7S*4v8F}dQ@~G<|OEC#w8HSHtI(SMcXaw2r!1=P_=>#-% z)|Qe~PR0lKDp2+jOI_P!sXBP^1>W`1$IuUv-fF!GN8Oq*y7KOU!KsPHya!CDp!>ko zn5*+6&}HV~@Li=E&s`GJVvQvTd6sjKMmoPIUV-f{WI^^(^_d|}JD_SWw>jvb5cKgm zyisdHTrn^g1FZ!-yY65%5Q{S>19=I{kiG^^_OtjXt{D|4c0vFezb=R@fycyeg}!O$ z<{)THEv9wmbb&c3HnEFoW+4V~>9q;NG#Ej(%B)Tf)al59D}9*aEE3#LG&g`3Zs-Nq zdZy|_h0?JBOmx`ZXDEKVPRTKiW=cHnOwJI7hzfO}QDvJ62$|w^2u4mR$bt&-eArZF z&H^OKcQBp57N?y#oEkzv5WvENfiOdh)9Ju$mA0$0$wUDnN7*WyA4qyE%iVA%$MdGr z7{JvUy1hNcT$)Bl(RvpXqh#~P+I-!}Efnj`d7X|y@|R`jy;FK6KL^{{T!r9a(N@p2 zID|p%Rj%~q?q+T_Y6^SMh%XKmw=KyMC^^KrqJ$7fWcz7Yi#3l`#xMaeH55oIRmeaZ zocau`UcpKtBzFh7_GwLZr>8kmfttG;=(}(}pa57wpRj#~)}N`2z`%44?=axGMBNM| z1mHi>Svp*ll#Mgq)@KI6Fc~@d3pPT54}&>iK5Dj{HC!PAqabuQL)8+DY49~P24rDi z&W62w`Q;Y+;It6b!?;|mij7Q7>fDCGK^eRB8m^h)aM9rm7t&e` z)ZU>t<|vNh9HhpgY`wUEPg(jf_N%~wzE#*~RGMeoFtwOV9Poo+3|b|*i^UGWW#aG; z8oafpLET%DJ$8)roVy48*2LKkJVPifF}hfA)eF$xFT~**FEHk^z*NnE*cFOX`=~tz zY2&y#SHWf5@|H11bm*0`YW8op28(ldwK10XEykB@X8oW%5tn?DPl@`9E zW*_Ub+Pn7_h&kI}0A^_;PG*?aulQ#g*n6@otMZTVq0oP; zc9bqDlJdLMSZQhD1EK%gw+Kj@3(?)i9PI!1`iIq**2!F2S_i>OOWQ#q6Bo_HDogQ7 zw$kS^cU-*#R;z}@el=Jaz#A~VbJ}k71X58l8^{QKH$-Z3zA_3c z(3N}@mofw^th%Lp*~?1qp;}R~J81B6eg+@U<55vrT+MZ4^NZnMK?r%Q1&opfUI+`mLS#c3E1jN1MS^EovTwLSw<=&+4M6E_Sj~JH#!c z$ZRcymQd`#Haq-}5nn6P4Zk?*nXNGP3$b$ukmIVgD_l;2aUo#Nb}s@$Gi+=47?SQyVZV3uvU9xj3vX=AmL zHkHvgY?SmRxfbTL6)6CnWT!en+faUuC>v~Md7(WxI%lL9R7AbIhtNy^=W6=C>Un6C4i@SlrU)ZXU-_Om^u2j)lxi z=p`+UKU$6{y)#AFJu4_G%pJ7^OR-^(;T`4Wn|ImXP1p)yUYc(2+HBLe_fw`JZ^PB1 zY~xZ3+w)PKLm9M|#c(-B!L4DoID2b#8|4RJw2adLulA^k{d9S%ShFTxK+6w6fMTgU zT`jR4Um70Frr$3qHj2}*eYv?LRs-7`9d$gnc&*e_E(W~$X`Z&urk0el!QHqZQ6em` z!ciI8DeHBx}WpbpsVJtplUS0xe9ci0o4tI)cNvH`^NApwH zq%tgzVVSXSyv7bGIQ9-owhMAoI<_yya3&9%reW7-C2yFQfH>S=o2J{xa%;#NuK8?v z&DPOcNnO_-RV%3ok2~|*Kv&t{TziJ_w8@YII71ELp(FEjzmed5DA>Vf`7LQh}y{dslNXX`RIj(BiVAD*(C= zlnCcWz)*BVN;oiL{)$mxRz=#tR25FFz(^S`2Qs~ZmMk_#jaj`kRi#IuEDLrxS`Y;P zSl_sF#T>u@kh>E1e8bt$X{dZSl#rQ(O~f_xP{J~Ko4imp89TDDaMRBM517Grcpa`c zfIgkwr9uhV!P!YTI4P;VOB(96A7tl#|5 z$7?)@KUmOfJk6^Xwcmt_nWS5eKt8I#@3UvWurHo=Tw6Z84hQr`0xCrUdxy1Lg1F{d zLRoE9I0n55jQ$r`A~|RWWU8n zL6Cn~%k=^0yivof6)q>vT7yG{ct4%XO~d8cc?hgna;<-~Q~*0-dKHtTwGs^HJy|1^ zrA~mcU9n(rK3anQk*Po?JSYRM%&?1SG4&bKvXP>{(IMGgCI4zxYSYvZWZr`zL3l>B zHRwI7#uM9?AOj0D!cLorYvl_Tf@p|id&+jL1rlm#d_-Z2=E+XPCcM%b7c|67dtrP_ zf*QS7_bat;lJ*>j_d!_-tRtJ!xrz{NYAUdPY}#k8u!Lt-hYIV*uKWa9cviI(SU)z^ zgf)l|hbt0Q){&hZvHCD=>{es_*tgF-;u)TIaH!CGc8x<;H%M90>0sL~OAIqB!J90S zV@Anv1*84^4EA>MzzUzLd7>VUP};C_XPIOgw&DV?Ek|YnI#FDqEY5p*JZmhw%{Of^ z1;H>~W6ftP-Z1n^2hgsi%P_r=YmGI{vQP7w_!^(|w>*3iqQ%?k)q4(W6>Z$Hizd9@ zz_nQiYQFa@9FNh0XQzFFxpJP?QbjmUj0vjr60vCW)rtX@l}nBn8_sNqtz$kOu?bxH zz+mX|i;Ro1(EH&U58KGP+!C)+G^`bcA{y3Wmr-hr46|+W)>4>rcWQh!HULGIVFUjZ zfxKRruWT7c>1HeB(wAWguXJ;5efi%2?vXIx#+7+i%dI~_sEB@xw41s_X_1+25NNG} zD0QVBi@Aw7ye`Gopa(mX<;U~g=X0Qo?8T72CzC;V)$hq?(37Kzm405BGR||TJVay5 z)#<$tZ^icZ!%hp&&K@_10Ygm z){m{NWP>4^*r~(%v8*d=Mu^rG^;kz1mL+Wn)6xok){(^}xw67ExLlp}V{hdt#1M_% zN~iT>c{<-7qM0oP){jj!A<SA)3~y!}_r-q_h#U zVTCu{mMXK3Z0$)28=|Qtx~v~dCm~65sAoAW1=f#E%?#DywOLz(^}$_&$OZ*>&n88 z?0KPD*rmrhval=hSBU;W6BSuMHn#3x3emhy9oCO!y@?t_G_h2f9};qUpGmc8%T0Y;6l& zjSiZrL#8r^xucq-gpeWwQwnK5h}Q2(d7FCK(M;052DG-um*>R+S9j>*Vj7yLAfj)5|*LK%Pe*oU1rp4a>uN^a$9^dgKS^YemsL?r=qQ zyXJL8#(}zCk8r>IuP7)KCoC%AizoI7@y#83B>Cx+J;Ho)%&rSp(y^fZp&&j$cj%Vf z>)z0}7LD|E{V#}m4B@W&AfGW_#{f*@brpdiaPFX*^}H67aM)O8bV z7|z&bg+QILQ*!S+W2d&=bjD7;K|5oYY#*JmQ%+dU*r|hG&e$cxPiO2BC6WRNYlUt@sOIBtT2kkh?*giT=GE*2URhCKp z*dAap_tSAw<@oG4Nr*p=({VN-=m4lwba3v=A+^Unm_yU9js6@w1NCE0xqi7ahm25$ zfe!h;j0Bzhee-EfA%40wryPGgoBUW=STk#dHUSS86I#wmIC$%5c33{$Rv=(EZVL|1b6YZd+*w19qX7+~7Q@ zDYC~MsHtYx{HMth zB^f9~^WHLDf_C3B9Rl><67jw6z$Geo&x1>t2kXLR3i#&3CBnjS;u1yt@!~Q;{<(3P zG++I=OrSrG+>szTEdT9PAz-)d6djzmc1Z1UC+*O*YrfgRGfcsQ2;-&~wail08tCCDEq=e-{$tbwFNlYqyQ5)FcLyZ$zD;RIA*UJzPM+J7~h<kZ@z^ms=gSGsF`fSi4x?q8vT zU!GqsE)=IPSH%~9uMp*%t5?YL)5|MF`sCmTMt5b&vrih0TD8(JLiO+pb%J*B3N-@s z@p27%-O0&;@dGNzY}_ zjL%$R?u8lJUzJWRsQMO;MM357TLlJcFW&9z^@NE<#3i5L-BFge-L=x+>oN-U> zRd1tKz|Ojzm4b8LZ8Yg|XWmBpt~vKMasqYs?R4?W`L~f9N@vhUCto^;b~1hIEZPb7 zv-4;t+n>$^x-3i(Flp%F#?l2f0`4qCD+Jd$2$FleGZ3`xn)4U<2I}lZ*?u{9K~5;n zT+qQ6=PkjRvU)-w2+&5=x zN$}H!T0;DBoPEPaNnZ+Mi@8H`aL&sX+T-raRC7z9oqPkf zh)~>^E#4PL=3wueD|5*3)0sKM_~Xu!!$VWyjsv;;z{i1HZg8&DGWL$n14 zH=MC#_V{?l(z9zbL5pLcuF00_msheRgyN7a>AuVcZSKB}YHSgKI60rvHY5iNq2AnM z$q(3tT5^N)pr*(kcc7-4UGtwN!$94qCDbqPX$lC%d78p~@tqcP-(069!B5X=30dxh zaJ*(ExK3SZ)T=}FYNIh!oo7?O3t_U|H(r~n>&2#~Jr5pZs%)4VLM$T=)9{KhX*3`# zz<$*d@cYzK%_tCve6{G5QE61`2&dDD4D9&hw8$(wpU)c&cuf4XC@^4C`$~bRVxV1R zL9Z8KCd%@7#~}1kDHJh7H{|(S%GQiLh9~stl96OuviD+rgQdOtQp5QeMVl14SbUdJ zuGW{*Gc%CM8-+mj?-@OBGtqKdB0Hzoj7(()5`$N)M=%|ZEkL5dnI$rXLqBC`!nSe$ zjCm($u%;at<92OiMlX-MYGU2lSc_d;bL=e*(a^DVZfFfYs7j+&ZNOKX4%W`cfv?cC zskyq*n5&j>%o?nDUH)R7H{)b>z8tuLkAwCLcAXB~uE$fu?ay8@uh%zxuMguitv_2T z7T|CS-PAR>fJX&`b-~Ahhe&0MEQ@BahCQBKL=r5#(Q>;#kJFM_;jWMn+wgjy&!r~k zE2BmwUCCF`C2zFn?ziXuNuxPmZ|^G1AP@$9c+e&xi{zv(!Jti{2sx7BLbXbP2aI;; zjR_dBRGJz1T(q2(`PUg43NIcJ&uOttW6G$PixnO2cT2A>)-wQMFs!MrwAU2eJh#Uv ztjTEF>ddGmE}Dn%U-3$IFJD)TxtyS+YqXG9-vOPziF&bvxNoOUpiMY;bE0PwE8&FTLEbIrLq=EDvb?rp+^xWcCen`&D`Hhi?@B2cuByZld-mu|Jx*y`wWOqr2=t0(d)YfzL zYlya9=Tmh}xW6-4OKb9AujUElY6E!pEClgkmgU`Xm(!ATcdz(pb#Wr|zQJH^;))us z_txjS0Byt0evQdmUj?#ZcfaiJ&hj6uosWY_o|W#qwPpLqXjeIIV0JrH=>lY%wFlUm z9{yHx+#jDBuMIX}`(bxGJy#ar)^^?4#1Cf)mbZyt6BB?2YvJQ*Y{3%+ZW!3F&aKZ( zmAGMIzf8=N90hCO<6z(bN5L?=(}rEUd`8WcJR@N1#&9#4YtPuS!w3P5>sA?+;MqI^ zHgEe;F$Q5U&xkl4ZLtO)0!983IVOt#Giy51XDu zl;KEvQ!kdZ{);QcE9Q-n`AQy-?{3o@MrJHgE*0yV2Ae_~=27AcC&#j}oTjmf5qnTi z6O>@JjxT4Mkdh2)yB{Nlc(OM$F#-pYv&mexF9$gMdt!lpV+q1K)B|=r%@*@|iDsJV z3*kU-%$aNqT6+Iry{^O2-x)aF49_qnS#f%vDFaGE3^Q8>l`er(#nn1fj1>k#p(AjN zkMjiu9h&ca9+F_N6#tGF8Z}LN5Id$kiJftu#LmFpB+p4~u&8ub3KDxZ@B--e6aa|P zTu`}#v)GwjyR+E9r|n_v?dsiDS{#zZp>20mt9wQ&>4o&gC9L@VTy`jzhPGfe1%`C9 zHMlC#Y=4f%*G*XgwLI7H^gtV&%MCh~#x`1BGdw@!IY=K~lW>ImHkw3;O+q~KA83o% z=#}bPuZ&T|1Xpjd;Tn2k)M}?u2(&3|_=KME33kGVKwH3uGjL3$d!&1CrKSk9 zHEj5Up795&BpMs-L6%l7#7IU!(FI4kk3gHiMhDS*9fX~jB+#a?(L3}`?;uN|5@=&^ z`Gt*`V^7Q?HEFV!K%2#ekLVd6;Yv%>su74ODIi8HUDlEvReTo)~VoO!BnRI>xfcweMA> zquOW~$9uz2CC&=3Wo!)BdZI(|r1%Q2ML3dtZ4BVU+cM@#MiyRc*cfp3hJTQzO$)SH zY{YkaCcfK2g111MhARNH;W2u{W2jQmZM2NzeHJ21nipuR*yu`nrYljU_Y1T=xcZNc z9R0inMeD4IaIROGHgWy07(0?X23mVAKfm!s z_rw=%UM2IVd!cX=EBzxcuBGjMS^;!RhLP0jJN`|3?q`7~Qv{*wt;;uX#tk z51A1*KKP8DW-a`c%yAfs5(D|idfwqW^FhS3R37pwxTeSg2x>?nLq0O!` zmfXm%Zxm_g8s2CM z$N6y4nH71XF&yWO!Oq_tXj9l2F7(WB!Ok?j(E^Uo)D>b*>|onLSHjUjrE5JNLeO@m z=s+97@iA+_l+Y{3#zHPeC{eql)SYzB252O4c zCXLo&Gdy2)t`0e$M;3U-YPhyXhh)|h^(rJHZx)RPuDocFI=VEi=lA4hDjCS?jJKA% zQ&wyJkga&UK3FK&Z$jEwJ-y=^w=}NFtz7+`ES-5Q&f8$7QYu3IS*w3q zYEG{dO2(8?FBdC1)My$qj5`yIs7Dg4L+UAQgXd7hLFAf>z8LG53sV z*3wz__Q6KOC{LG`FgZ7Co96dTq{$lQe#Z>&-kk5A?}xtg4zszhIIBGEsK5`ou>&%T zLk*hQy)nCdb{l+hZ1I*+*sdoQ@5n-u?C$NX)i+*a8ShtMD>Qb8irLA(+v(FGL(QE( zx?5abDTv!KWj%%S4S#K@xzggcQ?5fh^31C3xM5L=)Za4lG@tF%>9BBirpR^-v3wS_ zzeSX0F6=}<*^BW5^oxq|uzO`))@^E}c%avdMj}-o!=oFwiYtspG9K-K$+IX-Fr9 zHHrN#Do?u6i9$r`uR7|)5A8JU^aJZ3t?r%B8x7}Xt#0v_9zX9Eb8U22cBEK>k#6@I zq@z+{#w=C65dX+4h?DUDsIOP%gJf|EuVY$Ydf_%EM@BrlwA_yzDK=FOs)M~ z?f78@nZ!QVkaLq!JGDBjAkT)CF7f4i9@=q3;}NO9rnA=F+bPtc+4$0JJ8o3Gp((#- zGkhJfBX$0NdvDVl$+9el9b5i*hGpvl{qoL$azeGcqDFGBPqgXmQ(mv8`QdyX`;9&XyM$ur0##Gawi&o9f6lz2U_w==Hbd}~%(nCw?0zdfxHr6wO7G7BM zJ}s5$Zl7r5q|bW*1Z|K$60;^7Ls6g4XdDVZAs;_XuHhD9=Y881wr@%c@Br>QQgPEq zL++Si2P$eRMr(8GSme>Uw0&R1Hf^=MmV&a#sH$1D_Q7H%*ZN0PjjLdtX=1>KtrtFk zWuBhTY9COVUz-@|wwXCeJ8B~oJDD#11&F|3`@v%U_4EYe2}a5KCu|rj zYadWGX4XC!u^BpRA2r(uT000y2GaTmHqD@o2*i`O-tO8B?!8ZUU%=sO&5Z92Ocus9 zE*NVcG_q0FK9p%LtbM>NQ)2A^m}bV>2NYJtMwW?gTdaL<7Q18ZV@jK3?IS7X#@fe{ zrpWq7qNc+pxG}QQ)jlb4Nnd(eSx$qG-(NA`-m&yI`WyIBD5w%ycE^;0qdf*B+*)0fXK-`acF9I(jbHL)tU3 z7Tv&)f9Gx2)BSwzh2eS|vL5uvbodGZ3?ALB4~`f34S<{@GW~qk9i1+}x=-PB!=_G2tt?%CYZ^cB%mTmrO2eMLl#UxFN!ysi2KQAU-X zo38i42QP4f`5NyH;byIA`Wo36TFt2n&B?s5|u>Ly?M9hJ=O@uyVq?Dg2t0E9*t}QAym&>WUY!<}z?p(kfLN+ikdlZ8&nyI4W7&ZMWhT8+U_gn!fFk zsQMeiEI*^BX{?=)+BID98n%YKwj_Q{y0guV?Wh!PZIcx*;#g$$QOVoN=xWI;Tr*pu zz9y-hzhYDkE?BKjwGJ?K<)jW5yL5Nsb``&_ReW(n&hyl8s$mtkr{C&NzeM$BZ+fk^ z1g(Lf{qyzLAaubk_&C@0?Ot2sUMs^TnqI0u8>$w_)(e?tw{8$_)*%#-b)4=9+~?2S zU>*37g`6i;ymEJt#V!b0cIDBv_jT*PV|hzMu9g{0rt?^!C7!Od654_-LIiW;e2In0QWXn@MQ&q;F;s$Ti{ zG}F_^J>+<=yz87(69$HQRxIzm`pMC#CRV0^rT9mWyctB^(C{Cv%yutUW*eTwW0T;a zS=R&~8PQ2l=ladl&I3O{Yn|*FNhz|OQDYlkri|**4kFSJE;R+8lu;YRTP)S1?T{wo{V26FFf(XtNrcLpF3CYvE0N6 zk~_`4O)OSQLQDu7=?0^`$Qbq(_V*V*^l$y8zk4#ntYp(eVeNM2ZgB30b9cM=6nqXn zXSKoM@&SJ6=wrat)_;Vm>P?LZw7K-4uEL_J0cpxFm5})`n^V5UBZey%MpQL1P8yXD zl5@IJC$2}Hi|#9(URG!guW{4+u)arSlEcdgLsYY0e!vdq7QRtP~Pj zBVFGnMY`qFARa>o&SDDI!4&S`kj-i;ILoG0 z6&NzNs)7qt(_1@A)9SdB09#?YR+^JFr&_AR6)`6EV+u@!^gS8hteJH3Q3$xB4d=(YP8@<{4Si%jMXsD>Gpx-zSXgHTs zSS;NymxZr35hRqOdvLLIfl%P|)8#?rGkY3{6EYIx^-BczY@k zAiK1oqW$oLsiaMJ{f+ByCqGa+$mG=i?H&%s@K27A^Oz(p5o9!9ub||gS~?TQh!@mU zk(qN8H9tAxZnNHCrW#MpLJe9TjHk#;%4&N4?Pz*Nw(Oz1*?7Sso{Ac~#QlIFs3>+U zWIr5qE;V`wGGGT75Lp-vK^N;S{_5qnjYMdU=Y?i0r{q$SSmTfrx{4>~u%oP(U^BxK zlcdKKIyiFD?f22?`iqEMdHKX#W^w2YCP@Z1?~t9Ss%z;SRzeA0 znYTphnlh`eetm*BUOxU!+dPAQg9(l8#v5r^S~ z^!5EZ0~vD0f+Bs(WrswRO=E~Q)&aZRjVe;7A5C!E>k_V2i&1}#_rt7uaFNu<5{N3c z+`IxcF2l{$V9M~kGEU!P=9vN%7N9~)N^J51&snQu{D6_o__7uJnR zw46*Pk_VY*P12J`6Wj&*Xxm0DMAWA@-jkukx7e5@rV2x%)~?UlSXAs4C%YnRTedAX z?h2h#N{-9gmKm34KrJ<{Xj@`jdADC?;HYtlkz=I`%OTy?O$lg5=ibtNDoc5#b&0XX zbx|p%iyD_18RbsdNy{x%v;5>--wCPW%Wz>*S0(Tv@;H|PL$)n3Zu|zi35O28tr zK9_+&wk4H{ADmAWXTVhaqlsL`xS|3M#XUGEl@*Ae95f)=vp|l=6gS7Q5QK#OTD9X`~!J>^+r$ zDat)o3J|qXY~7TBMvZMP2Z-K785qcg5;0v42&0QKP?(nbGOHzaMP6nf ziUOO;9e%X-WiX+Yjwnfw8@2jz87x?;5|smG_!HC{I@_+p@NB&gFubkQi8Q*nE5jH{ zYn3AM@(-d5$593HyrVHCSs6;=}C3%U) z>M5v1bECX%8HueZ5G4Rm&nzXH+YTEgx*3Qon-XocBs}GI-2|gk8DQ-8of18z8Kz5g zr&`@hG*|4dC0fhdh7xTP>;xrx$}%jK=rX$XF3~uCBrMU1cBCuO-l#TRMgYa;K|Ri{ zq$A%cb)z*scBKQ6h;SfX@N|-%`ng@yWphj*bMDXaz~FA5sCTs)6#$1E(w9SsU;qh8 z&XbGorC)5ad56$9Y0}$ZWmr*I){hr5<@kSUJ zAi+xJ1l6dQ$_ZmVDt=rfDjq+M)su#YdOc})U}kK|S5JCeNqGARfYd_<28BpU zj9`KugtWh(ULQ#Q0l&-rwI3|jU-5iM1_%2d7xBrIwx5Q_H7;=)A6;eqrg2Uzr=^Aw zAT0_Fk1MQ&4G){5!8Z&^q`fyhuyQ(Xcw~z~wrL3T+x4bLAI3_MHZF0r`@e?gFOHlI z4=Zg+4Ua1f)eR3D*|-}XSXgzN9+umw8XgwBlGRJohDR6INzD&?dj#Fo@BnXk(u$d` z;gRh(3=IRJ+>$grvbZU0cv#aTzUkpD_oPjOpgI{}_)EC@Kf$y;i04Z zq=pC1u+KLP4T-s8Gz@@pGuQCQao>Zc0kC>>HVs7F9kXc&?DvvYK_Okt%E!2~r2Q

F&JpXj}>Fy59sPUX}n8HZQvMQtLt!Wk5$6%BlL7FN2;|w!S?R-dJ zWV+oABe%QAdoLk1o|u11VmwTe$5M~+)Dt}I7QJw?;sA4L#d73X6CVx1v+zf6aRcdf z)?2(UfK;to?jzB^Eo;)r=K#`&B{dhReWJc;8WWzxhmD1d7yoVqS04zMk`5#14V}FC zZ6NiM#gx?X(cco-qA4exvjiu%C`%CV{x}{ znD%i9nr4Gm07L>HA;(42JYls{9+fZpj>D2#QjPQFxp*F`6EvL)J1b!O2+E zSBv|+P=}ZiD{7hWmS(EJ@|#KTEh#R!M-4sek0;>-DriC-)CJb*JH1^$df;|StE}4( zaaV^O=9Q@BhDjTZtPxijK#;tgN;&B}6s3VR8jk^OSKwS6>?SbIXeK!7*{o;u$ZbUYHB54S^bE3BZo6rf_l*%;+dTP|(B7uW-zxIab%`ZoMAN(w ztK)H0e)yTIcPZ&x8s>{+NRG-MKlg+Gx&Mff$_+CE&wSBR{&)xfSi;MOS->2>XrVEUp3scqoPlu`dnW6p-Q!@*mrm5TNgpp1tQr|Sp5`5UEDc>FjH^iF1 zyJ7NMfoPf;S}~xB0h)@cVLsrsSX#;-Pt{ZN@H9+)W#}q6AfA6g^^kw=sghF_!UN6y zqWDC^^yR}=LEBE0`9d?{+o;;g^+>K8`JfwS0DbhM^1(o3oA?c27efAmg->fMESFEy zjW^%Jg&bb$DK~Z9(dqK5`xL6LC#%g;B$tQG>b;NNYQ24O?(V!f6x@@EaAYq>@qo71 z7-^avY)P+<$`wCz1F)rrS<@^L%hcaAKeR`}jV*D0(|i!m-QO}o^BH%}y`}qf1e5&Jl|r$C81-!wjkN z!uagKiofCr@(RRRo?}f70<7hy^&-+|qBynJAg-d>WJsyJ8ja0A7I;#Nx_e`Zx;yf2 zh{Kyvce!@K15IU7?uu8zP1E1+A+{2Zd>P!-?s)-J@Cm)u?ea+!IIpnEJ@=k_4J?(% z5wM0muT|P1-Wp3w4L+)yA5pX3zXtZwCe#Y8q>JTTTj8OK`g9M4MbI$$;>|>Pf*5KL zTCJk4%Btkh(bvFY$+K%(inUv%eXEQYQGNM>tyspz8bqANY)Zz(BOf1+ZA=xpS6{{6 z=?Cor@=)Xafx4Jy{+5}}#6 z+RaTA{7o;S_8N4n^cfyizO`&9*f62&>?;s=_L&`(?OFrBWIJ-B8(z8lVI`fwb;mFv(k7; zFpsFNRD%Jv6;oC*C2A+W0x6ZJ9%IW`m~khU96Kz;!kXk2~EVofO z`S62&nzQbOFC##f))yRp+MI^q7jLEu&C5*(I)V+~y3`%-RVeCwE*ZR3d2Cv{16p@L zOVP1cuo=c@byRk1eR3kx)=yCH^=!Ki!?X21fDFv9BGiZsW7BGy;aby{X4+zTAzuY^ zz6R;0O{i&6Mk={BXo~w9GK%mzw6B4= z@|4uAL1eW%q+#ydNY4+ULU=^`VtZ`ZPGR(dW2{EO{-fIwK%5+M%$(if|t`oBXHim@jx>KR1FPZiG_<#rRAq%W=ACCVsib;!gtjf zHO*bE&SllPjM{0h;bdq`sb#e1k(Vd#x8QWUkC46M?&;b?f8Ht)7TrO|j`H$hd^>X2DCs!BK-fTLUIq#?A!){-rGi9;KA?V#6;vs~5 z4IYG{ds`Q$W=Y9xFWxg`sLVhj9S58X0l&RPxJjy)GaG%{&`Rc}zjzN&8F(DGmw+`( z^%AH@a^nTzdVBM<@uFhu_-(9s3G{a<3Rdc_obb6`8wo(pqfPcQi{<2~%uZ6$@fARH zPJ+g=xD*&dp{3Cc$h`*1Xib}9qhWf)$&4*dtNUvKtmH(zEu)r03XG#*-_}>NB+yop za_qNJ;dQR0sJKZhhCwQ2f_RIB4McVSKvXx=*LO-PU<@y8eQm(%!Q}*vTZgSo-%5 zyQt&&BeXG3XAB6|DC!U0WWTt}y{Et^=4k;0GV%KK80cb2yH87greU>%s97 zVIIwwZWylbpjIo)1RnUe`<?^a%d*suKY?HA#>OH_w5$Z|-^jLEiu&W&#ooo6GPml}z<^!B99d?c;-8+7Co^8Fm z$pwCBp&1a?QBl&<`s(3qv1Mlp=nL*~40cp6a+)mbVl@(9KbI+tif1guL>(h6ii zvoRytwRZ;;K0$lP1gdvW-v5re%+tNO%Pa)ST$?ZQ_6nU59p~}taVl!RNhu8mmnjN( zLMTIh+Tc<%?O=34>s-dxvqDbUBUomH5sCNgVtPZcD+LQCE;>}mUc13P6MU3+kil6_ zM?(DI&f8M?78*-{i{tKCzao*F5H%i!vX)q9CeuV}onF01ejA;wgSofanbVb1L6{1U zC}LS0eI9iV30ua}eWJI3jZIVfrZzUB(DxcdtbWjIa^SH{q6@muW(|5o!{q(AXWj~p zkyqA|Y-~hJNMkt?lfG0?s5*B>V)_a0eYgWh>K@zub=79P*@MQ7R|K>JquWTyIYBiK zyA!$)Rr8|vrVJi~2xgZZiadqg`pw?QiwHvITMssYH_J7QjF%uo{xw(I`u-A>xS0=k z1nzE)YIqMeclMB9jdTMDPMobbczF<_o<3l`eYov7`2R=zk3)ZDzad6&8f+iH9#5!d z54r(1huelZy@}weAVnAJlMtBB>Gpn)S~Vhy&0cFHWjN_gpH{bPxR<$;2*Vi?^6|su z8g_u4_f4p$w(X26UIg})0G1)HLS3w@DtXM86BQ}CQEA#%>@&OmE_cPkI}l_%jKGC^ zr?Lx5>xUoF75Yw3TBlN$TsX18naAqrH8{4c71tP-sYT8k8&uCPTOPluS-Cao%Pc?x zdWs^b0i1hxegKvbP!ZMr2#y7yH>#E-y3<_lSK^r>%}M<0Za>mkm$Btg}VWT1|2!hx=iPaY$P~)=NgWRa69a*HoK>*?HbOpJKtmH zSi+Pf;~;BT!%!EbJ|v2A5!XMYx}t8GiI6h&(lkF7hg6+_gp)v;wVeydSG6&r4>Gfy z#HkuGs#}fTegW4R)lf`zG~JS`&h-Q|F|agax|BF+NJqi^BlwDKnLQZQ$H_S}0|N$| zACXN_okx!s9L zV^u$|UiW-ycK7j>j<^P1Pq>aqECPEW4 z2bx_t%Zqra8uFClWDGieq`jTeOK@sp)D3W`ymn?nNAF|rHe_m=pj4W3UT2Fe=Wy4X z1e9`vDSM}C5Cez|LJzWbNB7@&&sh}g*l_?37&hB*3i_q}S8;*J(zdnh^2l?7HY<$4 z7#{FQqaY8@OzQP~!W2~vv(K`oHX<~-$%9q2c5r4!Ny+9t3<@5?dW0jNmGz8MLR))*s4p+nM zB1eaYL{>Lr#OmkR8-7@SJ&LHxH*obc?h`R%xnPwJi z;#f7zYd}3r6KU9)jra$VABw8qr!+@kGn)6hf3%0&0sklJ6Osr7#7`c2f1|*hW6bbs5_)Dp+4XI@adsTq>N zyzFDuFt4#vjk=lHWsF$M93yYdrskMBV)b+Ebv|#JzOc<2viezeha!<=Gh|E~u7cU2 z*A=KND+tnJHpLqcuH4a0+>O%SoJ5h9(j)uBEpZ&hmHqlX1lwrWDXQO4+*>P(N%B@c z)=%1Q*VGY<`cVyTzWRX@I5frxGj|(;8+&hqKk-J2#vERA@AZK)2O4e$w-L-`TybY2 zmYFa=e)g#bKZ`2rc0@p=NFap0D?W7)GKH?}c%tBONNue0w3=K2B2iG^2b7Z$BLt@T zVLo~GR8HuRrZ*h0hEZ-I;~q;=U27%?Z=@B<)t2xfW%?mA`s@cerouiW5#iM`P(=)+ zAUCQ_&|u;sEo;u}$s<5Rup_Yt<`~Ua3qyHZB$K`#zB-^`s}Sf^E(cVq=Xi_p%jzYD zo+P#9=^8L>t0&(|j*2A*8OxhgCFyU=&(-sDj?#fGcUR2aQP^M0(KT>1WV~g!+QuDk zt8vtzaX?TO=3{6Xxq3z(vfHZ`w{@^By>DqzTLWsfkk=N&bzr!w-deOMt_sD}!-iF_ z7nam0eSESBT_o0Ob`LbF4Po9JFmFXA1Hhh@Tp>Qu)(x#1ON9-Y=`-rX^D7u-R{2cX zyd7B2d44z3mMu-$)J)$c%-W8mml@kYsmV21kJoINLVh}tP3|;+tVWm%nPI#8 zvT8}sj)fXyhD44mYkbsF*|d1|CYZa>gS>(V8g=zlJuPCZL+pTuPW;-39IIa&KUC$| zP+qV!i064~S+_dY9h$De4J)Ouns<4=s+M1?YgMMos`*G;R;>=P@!Ezhb`QfY+NMm|fwNs7XJncM)Dgy(G zw7>9UYI6puLWTy-?UI~CZl1yflxRFcLXLQHsUk!aZ88XWa>*=DVXS(jt=Qy?&_d9X zIi#gT;_Pt%(fW*N0$(PnenYG?KMFGwDJps@J8;Y?G2CjC4MFD=2%A zl%Y+8>t~Lk@QY}Z!6JFBUV#FyIVV8*U=(ESh>`s>miLu@n|Gba{^PwL0~Lq zC{{vzlEa2lQo8l}7DY^IWYifvs$mo_hvkqyB*9)pS@mccur7g6tR*CW5`9CKf5^GP zN`&>PzJ#?oXicUlLxV^lRU9InQGry|0Yw*rQUI(=Anjf>-HYvd#d342$@lVSkOoCZ zAr21`D_EJXNZLH@T;L>42GuX4kRI2_Tqrpo%^tIqR|CFH-sHe^v5ak%L7Ov2p^2b9$Z$2azQ~C1dAPo>N4|H3( z(HAF&NX&cg-p9`^qN;nxFNUf=}!dKTChsDf%mbzdU< z)*GXj$4a6)>5JP0q=Ui>#5pf2|Kf0N&y(@;2q5nwU!vgAYgfNG9vZ_9{e!y!->Vmh z{3w;i%VT;HkD7RaljuaHAYUN=5Q|^eOPq$8&hF(gQc79+7bj9lJlgE#5rmwGJUU7W zu9r8BtOQrjXh!Jwz-vKf`aUV&HebJw)wIibd#zNrWFEI_ok~`)-f%C0+LhpPatQ(T z3v{QUIg=0YhEY(JBuUCt%dE=Vr6?Mc=0K4q-Kh|J=#hh-({=jku)EH;{}V!6(%)*bezYV*>uFRn`7(oZasUX6&Nw| zH|hI3Z|g!m#u;SXzxVK2x5K=rXYv)Mu!jEFYA1cUApP6@&U1c;j*(GmRa(3D+z;pO zcH!PGJzTjVl!5)coc3qVY`W_XZ~cY81sK5F&s|2t?A&`uF$@0?FrrTKa=Ihkt+!e~ zdb9aDfb1OjWXPXvJ)Y5Ea(=V$=F7pYcMtVmmLvS*(OpiiZ)UXL{thy)I}ZEtn{W%` z^t(_?biT)|@ydYb-kl3sJ0<}XaQ7C@(p!OBWjL9BfPMzh;~si(K20wuTHjObn`jw< zJ0@Lwk21%IS^B95pU`Lnz0xp~a3`nRt@{Mtv5ZUx&Xv6IRH`k!o$D{ViDijtog1hF z%&G)O-j(?pW3GNnW1#t)y}tw>&fy&VJ$ICQXpby+j`KYVaK@JS+u#ewoO3gSKS8aS z(_&f9`R3D2CrZGd;xerE9+e1wPsTUvCq}KUbW`mPRsho$d`_X-H&%>2TL-Z5gur#+ zb4rO8&4A931BjDlP~&eAm=Pg6n9_gi2hcqkZ}1mUZQ5O;H#4Idoi6@K3!lYZe{{2oWs;_PJnl?Ygkzb7XL!)-uQ&pCJ?AS`5gG* zv?-vb1rr{>zmix%nZWP_0AfO+Xu`NU$I~55`X0Cw=5T_zrE=&!h=T5nN&K!+RiK*~ zQ+*D?Bk-5re{0OgH7fSr901<$tl zS}gXF+Kbp;))4Xa3#fH=sFfL0jwlA?j|h1Mhrz1%Ox-9j?PlE$fjhYo9>o z$?X>bj*QiVThu{l6p6B(BTy#D6q^yC_(g{zo&jzF$5iTIx6`0d11F9J$C9#*u}9B8 zT+KUFztBiQ3DE&U+(Lz<*_!E=iZTO=nb2>q0plF`)05ftdsRV00ti2+0abrk5Ra}W z9BqRVpkWMHnm99qz)b-5 z8B*Vu%37kDMYXiXv^~%nGtycf#x~-f{TcF&04-|;CL}2+xe%KrZz`9ihgyC z8IoPXmPPlxl6UyUh4cHuhuXdL*o;qrK+3QMM zoq4nN*IWwu5RSb+dW!;P)#tQSOK+)1F_}UImsYgC3hTEuG`W?oFJHPw@$xM-Up_UX zB)Mr8z+C%ek&DOYv_sZ#k8zYcWIZ6MU7Xb}PGN*L(h@yGQ-c9vB|31SwQaXXSv(3z zY93qgWdPIl5~?Mdrt8u=EiGzpPp{S`n2PPwhPgN3y&}C9q*sm9#fV-tGM_6bn0fEd z-6i+M0Jkaawu^fYRgy>T?U&y%XA^OuNpJop=P>ko33r4DAH5sN@@ygn~sy&5ou`t#~p$;igq($~yBhlo}*IZBG|Unmz;R#qy{*4)9Cl>~?aw zW==c_UT%`J65U>hK3tY>(bbP}0{2}5FBrVs9r0v>cp)p9iE<44j-UgcWOsC*Lnp+s z3k!h$5%ENkM(B9Co~uKq@UK-201)MQv}lhSAA(I99oFvLx%biCaO|RmaLCun6;e;) zGHqg`>C@_VtxB-Nrd6oFTo8^?!lDa;B4rPQv0#rH>U-2N6GgBBj--YypJpTiI?P%A z&3f_VfDbN|mmPt>+T7bP-_gkNHgZic(lkYL)bhY3gJI#L(`e`Ru4=NTN~ z<_nuy;<6olg#7Kx_^O4r87&1%g0o0xPyG{Tn;qQ=Y!hesGiV@VvqOjltPBj8y9ii{N&TdK7eo#2(bPdgf6%bgBSIu zjX90G)f#nHCN9fS+8E&wU}MqACb)+|8teWLSk2^5)q-mFUJ}wVI@7ybP;SQ?{6W<( z*mHFkcht-Xq2DE($5PY3RXX$3kxF8r6klfsaq82qRhU}CVoQ&2yI0UVi_TW1P-h$9 z0Mcoxrm6*$RI@w9vI$lTWjt4*-hZvU7po%EpcWxAA{U-6-n@<;-0%T5J-PYf-{2R% zx`jwPQ0V4+2+)EkzXxv#E?=Z8-YndmJ6So-)%<#eAu#KsSV6&Ntx9jEWysn=LR=%gD_82C(3XZ5DbE0tk-i_!FAJwp_fCz0LT}|K=^| z05b?}+-`jUSKs)VF{4WW4;u)!jBH)+0B_DUD2ou7SI9;t`!9NeBYnha1OeTk;!(x4 z|7@xl#K<@r>W50z?H)u}0l284w08~hp&^(;Jg|%Mm@N93MAi}$dep@+-$ z`p5kSkCvoE)lwQp0qD58yovOFO_z;6!{vi+8(P$#(oK@C8@Y~TCKO=dd(`9>wL)Wi zVgO%H58&%z;Ds5$2l400?k3nG=6DYeGperuNO>H?dYQ3%vu;M{9xxe?h*4q(lghOS zvRf?LWGo^s6+4jaBrX>z-RhYT$heYWtbMl+Xk0a%iMg$BhSA%EHI7EN1#BQoS^>O` zS&kU@(jZWJe;~qhaNUdfHx*+lOl8{5AiLSU#~4-`B}$OLm>$iXh%Fq4ZoBxI=4@Q% zdM9qql(7kL`C~IAYVRW|NmGJ+4=|1=;RG&8Ahvly3(efZ)&b(<01sn9F$>40EU*N= z&0~!!*lX@PoO1oLy(bL`N#fxU6cp(*KLSehnz8j7*D(6tn{gMTeq7KBs#I6X(XyzK zQRy9)j+C}3Jb;4OjNU$g{CT^}P5FWyW)NLhg+v&b1D6QeodF;&&vM86?of@0w;?X~ zOpEkZkT2v!BbLakCG(UQ&KZfJs1UAY=u~NB$4aCghV3HVWlB=yQaMAJz@s3QWCzO1 z%}J)HX>mH4g3z!A$3!@? zrU*;=`lDUTvcI%Ry!bpUsz*Fo7hIX59z%)yZ@%QHq$)ZpTcTx}$|YTF84D}fq&{q0h)*mkut9VPFAWqRYgW|^jPmo3xS%(zoZCMdgj z85p8nzf5ae+VV0S7Sk%q03)hplmI|zEoFwaU#gY>BGQV=fFTOQmKa#v=9Op)ff*0r zZT+xb!l9%T|LUO~WoE8aYbrAXn~IpzsL}0l4`eg%vF5*&RxNpa?85O; znv~Ih(sZ*N$|>>71FMr|7rf_W=hW`hIS)6yDK`=6DNW_q0+b|gRJ|G8G6>&f*#kGx zu~esqM=f=v6;7Mt6zLGvgO)i_WL;A*I|ec0As{~{oL>}&TYyh>Glh53;T)C&O+gmV z)eBwVox%mI8t&Hv-#X`A?w$4wK_iOeTuwWSI5C~v!8G5&jVCpVsi=!N>Bf*Zf;br^ zCojz~iuX42~_#7{Pi)TljWZtl%?IJ5X>RW(T_oU^wUU0XCK_s|hkd;JY)WHlA(@oDH zG3xs%!Bp4uVvQm^5>Gk#)mHkl<=r&iwO~xUfE6j8@Cp`hTx;NC!$069vv21x_Eof? zmTS|vHbPX>%&H09UMqC*I#sveIxXv_v95uwqw;U7G|*#@b|=xYZ>p}VAN8B&-d0r* z$6~{1)5_dbnX6g2nr2!}WoSh%^>ko&#I3ANm9=@2AGPPPG;_j%=P_ZAZ>4LRbmdrT z#j3vp1IaPXU&`%M%d%-K8zHJ`u5C3~slnPj%UX>fRU@$8Q@1Q!`nusj{Xn`Lw9+5O*ePIW~=B!NmfK5JQc?Q>b>l_y&Gl zO*5}1Y}<=5da)3Xzi9Q_bp1Atv8MUB6~aOYD{jTD@J$oG5u!4fRXEdxP?VlI(^wt5 zh%G}k7x;4~_w4-u!doW@V1CpCDV!jp0sUlfR znRQg|9kj=K(3X{4G%e?*ac+dDqjGI~#$%VMvgnaWzt;-hboC*gx~geDeoi+316{KZ zbQR3ZP1cIyG*O&|zNQ&l^LA+kG!@YHJbo=-r}4EtWo^sN$2VBSb6d8YoXW{Dgf-2@ zt+F9Jx9kNY` zn6}loLK20fO!eDxY%0gHI+A*XmsThdTn3FoiAg@$W}(EGvmvd(k|pD6ER+ZUB^64{ z3N1L)gIgLaxTERkm!K%C`OU_17SDSDPhN&^>mlnAY@# zrY}*fwdLG2&gEEYTGY0t+E^aBs4{WOv1uGD@znC{(TxTJUWmKp*ffsiSZbPITXj~b zv!c{|%ciMpdbSRapt`nfD%g~C&Y&XT&JA|01vb+HtKzD0$y&)BZx_T;u<)@`FUA0{JZCvzQNEfCS^w4bY$==TR6j~o=G3VRMamG>!*A5OtZfqJI5o^fuMt@n zHT3F=HE@(sHn7HdJZ!!20cvxhc33?f@d|GFA_H za)!^^2$3>m_4BhPe5`G1#F~+>>EUX?qL!nrw)sQ}wQ4vLka)VI+9qNft9q(rCB3O_ zdYJ=PKUaGql-g#PFkB7uilxPuxG?I*ESmw)mq;G zJP5J=0g6xoZUn$$tj3bU?Zs^LX>+r6{hbyg*^9+U!jrh;I6O4(&+rjmJv_QwAFe;K zkP`$criFpeNJ@kxw8gs^yh*cNXuxYkHD0{gUIQ>m@sZUVy|o~}+9(gfJ(KGTndGqu z{d;(W5bnIK2X;qq>mt@JW6o>o;a#=OD`VK|=V?Sq)pd9=hN*rYG9aWj(nKRv54{#8 zP}>ajQEHfl=`>#7OkyKdKO5n0UDuKj{Z|jUbSSM2|IrB5LoZw~>%;rmO+5x*;gwe# zNV zZ<`s>>G}&8^%!I3o@NwK*9cY*9j-7+D;h92J}qTFXCz*iEQajM>0s8MKr9(JHBCp= za&o-#XyqUkrb`oe;y@LkonCJ>MxpQxmOf$g0_v z7$)Bg?v1v6#-;Hs5OPY!w`KmZE*jZGTQT_VKByXxs>PJt4$aNDjG1);u1sZH(3qMv%*31 z%r)_*EjHbnglW=hsAT+H7)V}mmB5f~6-rt2l|YaW3yE^25*YGepnZb zE}qOukQyP#O^o_s$RtF!_t9Iew@=R9oi~pM(Z)zn%i5?DhTO!c6%!nas`dXUu0cBi zX!lHo4gs+vFKs;r-TKPidckf2rN>B)svq zD?e~|>un7NI~@m_TNy`#WvvXO+mMs=+mPIBDr818@D$Q3G!n$HkxO_NMgysC(5e@w z5)qWKI&<0ihSzTJqqn&6f^fY>Be}Nv%2>7O+RBm2V>~;2jq)T16&}*9cW+5Zig9C+ zo66!$88_6)Z)Wh&ni}YzikV$X{KC8i; z=hCt8M^7>Q4$}9PoZ4_*!{=T)H3Pu#PfpgG*}}W?1Dd;N=EF(j37*~z}Odb{n?I`UC1wH3bpQtut=RVM+oZHAz$pL<< zT-XeM^0w=Hto$3h3yRxj(BASwE$4XQ_N?pyd5S``G)WH? zsMCyVKCHt<>^^-Ph!|=X$wOnz_VutDtPQKdF_ussy3(4eE4( z*}sn}0R?`Tl4x=y+@v++3vsXnteQAR%KVl{v^?E2Q@izS{Vk22BaL{z{gyb-kvJlE zeM_C^NFAP4yCu$ZBu;V`@0MQAkzUqB=q;(9BdMY^=q-JoBYm>cpSM(dj#RU!bZ=?( zENL|`^SZZmdX97wY2;h#JV)y2iPBpdJx3a)Cw*^e_8e(O%nal$nVut=c#`#&IM0zd zGK+jmp65s&dm+1{(yITncUBP?IHEs6l^|4pWvvw{JSwjae)ha7FmOa(trCQ)c~uq^ z%3#xzOXkEJf}@!3s}c_&hY;nL5wnc0g=F|?a~iJVCCl<9PblJ(Wy4{!Y*`lsd9aX# zXbT2vNgkX)=SqgJdR2-VFByySD6s_uauA2{Y;54Tmgtn!tP3xs1R74EmY#5JJq;F2 zc2WQ9oQVo@akL#1m2gzHn`HtWDj7Y`?1h_W8nR=+)RSndHV~>_*ReA8O)qfG3tH&V zt42!5L@lwqmoMa=C`CWNOf{2>aVu+sm6YT)!T$@&ymOx_x32&r$Y$TmGkeO zXp&s#3bHeeWr16kph@k&k6EtB32UMy$xLd=>&kVz)2G$#da0=GEG;h#i0Qgy7SMd~ zh-!6R1_|ri0)3Hvkr39+4HispXd|p{Ocub??cMQ`&NDxNYQ=Z%+@qPvG_;zftR0Y2 zGt&F8zW3+uk_W%uU=^av_p~@5+*|mxP_(;!I^FHIgIlPE40Vf=zvy|v0^YB@)%;-CQg(@e%w zn0Ro1?cG_1BV%YPTV272KtT&rGg8X|`wTT$pn4sgZttTR8#wym2gE>Mr+~7?IcmBi zwL``YCHiEj>|;8KZU-iI?rnf4Anh+7CS(MGyB11$R$m)(M@J~7Z3*HNX0=~IQp0?EtIas;7`GYxNgt-G|Ls;MR3`Qq! zj*>Mk0{aKx7kDTHg7E1Jw8>IyPSzAsyOFy^Rxd>nq2(%dnxd>eYQ1whoW7$Nb(8SK zWZuc?cI!TY$U`O=d~uh156<++Ml?oFXOvpj2^?Z8SsQYm&|yW(AnLL^yPOgb&s6S% ziF4s@F!^|XJf*If*un!XFv3Y{Q@Pk;u=cPHN#&r*@%Tc}AU$kBiyjZ*2YsZKX~*3mWhKT!AzTj%+nC3?QLCD%~0*6owCI+jCPS5cZ*tF zmnv^JUwA5l*axYNiPo6ve)zrYt*@(cseN@6Et1O{TOsy=3%cQhCJr7kp#%1v=iE6) zK|qf}w%Y8uQ$ytHhT$|0@!)pvFBf!^0ls*Hf54H0?rnWZx#`Z`c~s;?5TnSUgiJxu z(7l7~*dFPf(w|e<&X8jyl6VK!uZjW2wTkHw_E5OXG`N^C9Pk9w;=KTpcJI!J^Mjnk zh`&4Q#?NWkqtVuKlEA$?lH{;uIT4HHKv9>W=)>B@#0Ci%(mpDMphWDXfY^t##sg2M zX#l3YDhm#0$=(HAQGJAgE|%_nm+izJ+?l%YAN)6c6A`W31)?sWz!J0r z14p#knSfuvN04F^cIflOsZew&jiCQK|2|qbi4D)$M#;D~68XdV)&n7rTl)}fc3bZb z3IQzFpxis()7?PPU2C}>YUtml-Gob%16n@%+M6lr4iZb%g4b+Oe;R3H(n1Qq6c zZ;P68gqyTkkIy||NICEBOroKJNh_V(ryYGzY~piRGsCv>d9>KmM3Jkhr>7{c z6O6e0FG0W@2_y@ z;MC$LXS(j;;syT{GlzR}X2VC#1Bl2_oTa-n-W`72FE8Ra(Owoqg;=$yse?XJLhs{) zpZkx=kg=CXf^53KO@@rUJQ8G(HZ2)4_Oceptm0NIEW+0E#d=GXjpejIga2hp8^PXG z+D*=Z#n($v$*Bgz7q-^3x#L>CDKPitatTXJU9((;jBkK+_ls1eXwAZw3A2;}AQefe zVmd{Y||G$(wZjr62B~^4iYz!4(CIJ^Oe5{BphU zPUmxv+9zRW=?3s%#tw{NvZVVREHSPZJ0EU4^#70e3;ZK^MU8!2!BOD~1dzl<>bf*c zTlB0Xwm7bs7Uf#&cwCjloRU;mn!ru^HqIc)GQxt>#Hmt3i4GaK;a^W8pbo^ibawAqLg>QX#$sOc{3&%4&o0y)IFxzxqreH_jX5Sx- zo~SPyg5=u*D3TLM@#h(Dz0DGwQDHTjxBSLAO7=!9b|h>Ycj2Qd1ofU|syKHCrj{pr z3!#-ns{2Y>nAkfeAo`2_3hv`1Okx|-)*qn^;p9cLJX!?V>U*)qaj z7Qz9Vwy@b1ctC(pHM>3+2ZPa}wy#U?J{el}vJkXlH$2cJ2R@^ybr@oWW0<`x1s1H7 zXaVwbpT+FMcm1h{j^s&joQT(DAyI*m%MTBalOa=56k(AG>&4a0dYXu{6Y;tjtcki( ziYO4c6B?71h9^`0x*~Wa`tS;aYOYjvDm?;gB> z+M(24A(_zFaFk#qQQsAVHf|chAe{(EZy9<8fD}t~k+y#6%VJ=foCpEOC}O(8uJ#EG zws*_**ZfW&;(j>Emx$k8^Vh|o72-MWL`3<3Z^fFjO3*BLxd*S#aKGGz3QLf#@MOlQ zfs+qlvI4)OZDc9I%bQ63mLEjgpxy&H@q;BeQ?V!yexZv8nIU`PfZ35>6ZM&a4B{5R z@eUkLF5v>!u$mdhG1#aXw-{D2@u#Arrl@!$7%#LacqqF$@(eG$BtJ)bN==EJAVxn~ z9`hvbvYzShnG6HdHVqJlZZLlUckM`cvG|iw6o2xGITzj(r1u{nG&~@{#I$gx(=D=kxOVJ^YwC zz&+9jhk^&xin+f6;@^JC6^uSXJ zgeou%TpSPxnWQdkv%3JJMj=Cu%9QjJ`)P}wot)W#54*{Hcy$9FfS;F-z=!Bp6WQMF z^8%kX`w2IJkk3N|q^__(N?u_%4*?m&M#r`j!33ojn&^&7^y0l0S#QOAA8arH6kV}k z`zBlW>ue1!h@QK`MXW*2Z&{ zCBRdME76?C8*qN443r0GE@ex7Ujj-t+@X}Lq?c577WyIFBk-Vg)JmwmbS%^uC38S^ zK_|rOi(Jz2xW8M6ZtrBX-t3oP^0|S(Zq^_Cz+K)){=YoZAY@|O;);xl1XD4`)Pwk9 zziixnIHnz_t0nf5uf*wY_{dz`xvFMkSWU`+<21cJ1ZDLjY{S&^7F0v3tbI@4IFFa( z+3Y^pQ(tUw$qv?A@TH&0!wp{5!!`*n_o!q_+}Vasmse+9)NY}4E7xwpm({A>63}jY zG3}NPO!bN}_4NlY0&p5Zk2_SmNS{p#Gcn9=!B6|@J)RTNq;Wd1kseoKqU6Ad3b1p? zI|e7>15R2b(3hLv$Pu?!qw=U$|F>Uf$4G94kiY@q3eX_Wu(XO>G z3fb8@vj{9^XUMucrNWhFAyR$bslk}yLpdWQ8)6yM9pOL~$4TsiLbHlI6pXlRrs`?b zpjJah8pGhtU>M-n+}9gFpuUIkQ(nG1KJ07o)ekLJNVzGid8s7Lml}7*Q~y5j76CBl)B_{U&Yvssf((H@oHWfkA<+i_GHY~%HTtdO z#WERSZCxXtb*Eyds-uVKT ztC*?^9ZvxpV9L;?L$`xyh%`?YW(@`dYT8t76pEaU%S)?;fyN$?lSn$Af|mOf`U)v} zBcTYhOLn;F+AShzq#jJ6Nz;S-UD46>?BZq(UF^2&B{=hbEz=vj>rjBhAZ}38HB=eE z;Y)1~VJJ)mdMJRtzykoJAL0&>m5bRQ&RHg-`(&{usd2`C|l{{?3%D z`o1Jp2_%EKmn|^5Y@b4Ven&Y>2BXhp@EV~o2`V3ZGQ^O7U@5JEhUv2%PvRv zzQKGBnZ5>RD2OQuVw+kX&p0Q^Gfs{8re;rRCz6i*!cL^;kSv#y1Q+aAce|MIn^V$V zxj%aJFWhEvqkt!2F!olEfONaXBvU-ZkY6sc z?omhN5a&*d#|v*amN`v~mvsYNb{+>@6Ki)Axe+MpA~uFWIbhW&lpQyY!;|Un_byE} z(-;<&A*;rt(skpQ9G!bh_X#wg#t<2eST!D_o*PGFtn6Q7=!}nCHCp2%G>={a8)p!1 z*3|o`Io#^QR*qP``^K?qY^`vXs8B6{-srhM#mTGamaWK1PKtv-R=SgJYBfL|Nu?@) zj(V#TX1TLh0kZ6@PKe_|stVBKz1BJhT^>|20lmwd&_nSSxW`;V&46pj`4}v~1}o)n zTuQN12R#yLqrvh=S3PcN6V9^K^3x?^fH#mCtc(A7>-qt|E4oa2&F_TLz>{61OhMe^ zK*a(+O$i0nhGZW}?*$xv(f)!~yns|H@7&c2vcJn7lD`mrNI2j5_YZj1y_j5^y`+7^ z`ynF<%f$`Tjs+05SG-lPBU*jlgf5aCU|)X?sD@GsW97#lh&`;V^X?hm(u_Mc%L{+4~q?ic%1?=F??`0 zXuknVBDLEr&_YTD)%z@dOam&mdn7}5^Cg)BfI;B3u>IoV;)XzGA9{nWhRK5kyF{u4>@(8pus?LwRmVu{_q_#$Y2=MY zN~G)gZ!b?jPR_oo;NQOjec=E9Ro>Sx{{s6({QN8Yx%h$Ks{j5i_~YM%|BLp0-d_Ct zmHILK{;wH-|3~n*+Wv;O7e9Y#d~de@YexGGZ!doS%4q)w#^3)1{QYmk{{{Sio3|G~ z|75@#;5>2eqP>92foFgI=YPHy?ca^r_u}VI9N>SQJ(lB$@2}zc|EIM7-w+`9Cw~5d zBOJ#^(9HR}@a$hG?f>+@ga`0X{QRqYK4N?UKl|#t?|y_|@Dl;g|9i^bh@b!RzY|}w zpBx`{V-?9ROs6GGlyZ^^%|0n-DYxgIApZ)x=*bni!9A7;63oy7k z{xAP8*5H@_&}hgT2>3<&e=no`mBDak`@anB@tK(B-}sko+=qY6es++!%D-PJzvHj} z2_L^>JQwXRjORF4{3m|?XYh-V9qbMJ@BhTde|X2f{1rU@TR=nszmQ-5IlLCItL=aJ g@3WP6DhA>&jX%E|C$#_Vf56)R#Xr#HhuQxB2Vm)TivR!s diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/dsp1_Debug b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/dsp1_Debug deleted file mode 100755 index 2835db055a2fae5e5c135afd89fe9f396229ac65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1351496 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 K-vrln2;2-WRT8WI%y(^G!O2v+v* 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 z z6@T5(BVV>6#RfFtTD8xgfuxMPJW}sDzbN@t#Ztz4`&lQcGR)lOCOf|PYIEC@l$Ge9 zk|9*$8aLXq$n=^*`>JlGtvX$CD2+RhtxTUI&e_=dsQhWDeTiplAF{>T_xe+Z+W$Vs z)&6Qw`-=5#wO@H=TkT6-?QcoGK-A7*f4gJttAg5B1+_Qi)xL^3CB)1!SNo-#uRTDC z?X1O1_et{Ftgm%zVGI12k-B7ggqI+zBYjU#dU3% z*&1HJWz*t)g{ytJl`EZMCWM_SN-~+oUUje^n+N-b5cWFS0rnp;2!OrW)qck2Yp1$w z8|-v`u&rQ7G=TpJ3qKW}L51L_*&-b{+BkCSE!N)BR=eZKuWoA3kDwAbaLZ%_HgDn3O%#pZ82KLaUCCrCyGet;=ndyf`MF`odql zvD@hMPkYMk!n?=l3{mE|2y*bLY1}>h9tS`&J2v3u!Q{>zoGat0Pt8MD>EuqK-(`8~ zEbe!58})hz$j?kGdbE@tnEPFpmW6rJLHDOZot=M&=QG}*6@TBo(2mBZs>V!;dFOmA zJ#4=xx7Xcenf_UPMsWo}mDuAHKGDkV#5tgyR(7Mj1u~IGZgePJ#rM8=r-@LK21nug z2A!T(_*HMGKC4Od%HmOc4X3>6l^qu?_sUL=mY5T){2kKpxn!tQ+Rl2_9BX4EM-!_G zmio<+U*g88f0S<`I57xX&(7w^$zd=Rso+!706wje0es^E`WrSpD05U>rm-&JZI^IR zn9v+KF#JsQ@25Pe$S8iABgX{sUhjzF%}nT=?$yU;S(=U%>cgGD@~*hFC#XsH`EU=# z&5f>-hNmsn-Isuv{gpG_=Z4QTSkM)0Vx&N^iKbHZcCDW|x(KL`vswBqKh3fKVn1^( za--Mn*SEGNBG*&7b%g~VE9l!&!ukimhpOFlWC+E*vd9WT8pqBB_GI+0ROB7y$V85z zCN;RhdnB?oQS@!4audZFj4914BGU;LOxkbew^x*!vY7KTLd1{*_$GEmWC$N!%ml@v z##r2OTcK67EUq+ZZ$Are$@D+QK@d61QukMc)VSRSj*KT1&n$>GFr>})5Q`Bc2Fncj z?S0IjYZ)N`f6=KFP46+E`?)QJlF@Rjjz{LP=Mjf?Hkd*SRWxIsmBq(?b22)RF#iE^ z(!rQXM#m}-yME~P%2G)XRR|m;qcwc*8z8~(ZB}WEI}^DKp~Ie%)gG>j-YMOd3I#KX z@8Vaei;;=B(Z;wsRez--9c*z_7h0G_4*o0PbGCl@r|4Vwj_Moi3ubc$4QsDodxE<) z3y5BI_h_iXA(_Yp*NHa0cHKohGZl|7l9wwPmM|n-P%N7F>Px!y9kbhAy|yLDV-tri z0^Ur;mrTB}%jLU@puN_+66I8E`O53P-_u0FUdvZR7V;&o`z3jsBR49Vl;# z+6J=%LkIm=V;-9kS4iA);_wV{%wp!kEK9FcE|z5?zJy}rkEmrruTUy%$1@K`S87cV zk|h)QJI-$k<~WD!VhQVHOuXf6;=~#iR3Qpk)>y917+R|d86z@af@@7QCbw-R;F0Yo zyH+fNz@L@WDEeDhZ8URTbVJZ1qhAaLu zvL85$(>*PIxB^~vx^+XVeef+P{Z*pye4@~TY%?bI0O zKC%hAxV=`u_)4}N0FEcfCM58=k{_D1aIE52PqvE51ge=J_q|Ny_v2^?Tfzh%2A*v* zKxX}BA}PkPfY4m3ChoM+uHGE1v~T;b^^`hIySMN|6Sm!Z`Q1U^v@6hz!C%L^8fAt& z(;Ml3;}Q$UlQ=Os5!C1MKN6FQ9EK(W5G6Q!Np5_FII4@5lW;x7NEm6;E1FKid(w@G zNK{eWmp@-{4>^Z?h?YO@ATbmva@GCo8YhODyxtY>k-^P!FY~3m3Gy~a?o)_=&5=if zFS8^m0btP-x%>{QZ;l*7+TP@%?kJyT{H=gZmRd6=_?m8x;Y*TdnEh=vgYEAcbBJ;v znGV!%X2`N4%1^aTEN~paK%OisLBtHa)Cnj{Uj&p-Tr`Li1Ny|35LXn$*%3^fE{FZ5 z!IiZmmAD$>Oc0kZ1YZo|tVAN8gkTeKRsvS+4|D)I^G*ced@C*^4}74@#4m~L2Gvj& zufy*MjKuiSKPejEwTY-B!>CkbFFr%9Y&R81N50_}YBKHJmT9O}p^F4NPG&@8L3EO=hobzeNg>o`}Tx;wWC^xBRY%oX_6^bL?ecRx0BjJ(RCG z(w!d-U^CLDpgr|}=5}!xD)Sn%kC-PDISWnE%2dvyR{V6VKty0rN)l=ozqW?=p0wqV z`YO6QAts5e;`RqR8tZ9W`) zW0?_FuWcent;?}6$Pw_#!$B~fs(6G?knt)%&5@UbcxbA4vkhFC?lXtabg$p@H=Y^F z=~ONT7>U3^1G$hm$j2#N(Ubm^5dkhf|Aj@2pX&b40ZrDEdQvKKGif3ytzD^X>Bv8L zeF%D8r;sIG|KI5oeHm91(qAAw5!szMt9h@8#Q0SDiTs+I?U2F!XO9<@y*aXyAB*v8 z-CznZUJnIzgelYiAGn+V-5g|?Ra)mn8!XcEh&7PI;r2)*^J<t(Snf$V3EHcH(EPt?kkpZCW#3$eM4&8cS@lg%*XE+fLV- zQAix?7{KW+IX4aLEP?gj7##^ zwjAT^VR)WolG)0ueu6tabO55u6W(tYk18|X!%ynrp2iKrjdq;&p_IMtpHtX4t)>q@ zI@5hCzgcq~#K6pTVWHnV)N-(&n$CgdkLm{5!St!+ij;|$t+95uG|P6lI8t-*l^n5Oki;6BC(X>I%!x#@zEQ&<4I5g zMGZHQ{JZ`ozsr}Jy;kVYlQ8>YZK>^I&vQa?**D^4ADdw_B!H%NkB-uvyH#HA?osRs z`S)(d7Oiz?or#;FeI+HH;=IQ%DHuICOW z-vy95AiYJ^#R=a+hY_@m}D;6xlruejP%_3m`wKr6mTbJVPCFtnRx)WInNx*z0LJ~ z;o=>!B9G&M?igV!|6PsQNECDD#oAVWcP5(~7T58zb!PZ+~{D}BN6yJt zpo~@}M&O++T}@yu;br$bX7Xnr>j{ZmVAcDib*wtxpnG&xJ<)<4-yCwOJaXaj*P zkCN-0cjD`>yv8#S^*~Sw1dF}xUxf&lyaw{+8C`LR8b6Z8pF#ys3@z;PsaF*97nM_- zYq(3SA(s0VdPdElX_loiX_BQU-m18I7Uj z^X4O(it3`1t5PEwNp9WZ53Y;WddWc&1gSlFX_pkTsK%=F_`<~3<0QmZvD z#L`ER*GrxTLO3!MOEl}+x>)l_D|RG5-qdaL+Xmn^QjORE)6jY|d8J@?%eB1uI?f82 zT}!c6yxr+4xBtV+0_Ha}tjB+_1`q@fSxo2%Kt4hn@3GO2Yw|kDj1V96A1@h&1%>8* z4-1wDt>))w_G-Lrl@;FTg-yW1OLqR?L!(}@5PN~2`3b1SZ~rP=f_z;iuCKOsyv3^` zvCNL$arbUHs%?L+wlHx?VK*&0?qqbqoD8S2KuPF$VQ|?^v|nOrLR65io`wIvJT7=!oOGfZ^aVF1iz6 zH73~i%VN<&W+_`i*rH2dlMoRhJ!7qgvY#GRknM+FhJSe`;FHnFLp9PwVM~9@t{;)f zop8&(WAo#8Z{^UQS)JyZG}|0l2g7G7b{D+tCeFPdZO`tpF0hO9Sc)4kM;660)6gXx zgP6jFT>m~%&uFhUZ)m(^Dy})f6+fHee)?>p7FgQ=#eKV}U2luu0&wQZ1>~7n4+U16 zn_cnP&s_0?R6J9$&2cWcmkXW}1dCiS8D#F_g2RH;Pch+gu0fp`sanB>I7cHqV%~dN zaJeqIm=}n_W9f+VoyXELLAKXxer&}o=;;G&h-#ehvRw|yUSTNewmVJS-AMlMi>0H}~Hv@vaaW6UB zT=c!fJ`!N|TC~8!znmVKhsc)whHA`iu6)m+pL0|mVm<+W7<>>D%+@d+)Iw10f~Bsr zEN4DL|L`?lkTUiV&Ju^P`Z2D@qg7G69;-4c%HxcCREOn^R+DhW53X^=ceHR++#l2y zalzpME3CUmP*zL{2)Eh=%YxKb2|hkfya478RK4wZ<4IkV(~dVDA!d;Zm_5L|)YC?$ zD^u_*i`qMbYA;nQb`qg*1G&RFM#EZ?x<%$xokJ=6Hv4D$?W8DX5@vd<{x2F!Id3N? z$-JGCwjQsnXVh(!{$Z+}nz^A6B?{c4COpriETbBiY!KHoD&jP$lHQY%Naz#s)aj*u zikIj!kww=7aB5t+W@}W{fkcyUlrQaKC1v!_D+>tt3pAxz>gJM7$dqY?FUDkS+-K?+}BC3zz%+mlYw!7~vdag$o9;}ZItm{}aa79mi&_BK8-@pl0 zW!Z{rG`&>HP|ME2$I>C07$|pUGJ#tP_xJLCy+xZ1=$iR@*mG3SZEx%!p|JK9Bj}M%je_N z;jjUpRzQpuDu+hRpQZ>eoSqVMSrRMRnd`fk3pF)b;*Mnr63xz_DN~~okJB;zo7r+R z3jXCWXg`pSg$lma@9k^Cx^YEoq*_0{XP#=0m32Uq-Wc34*h|h6l~Z4M*NEm(v;$lAO!hPXLYLX(rcpx(i*-i5*ZzJP+ZUKXZ0 z{TB~qr#=0*<4A_j@5qv^v~^Y@#nu=DELHSDV!u?^C})q))o<@R^V2(J7pefWNQ(f) z!gQaxZ!*KMZGCMQ*U#Wk@)?7WQ4Aj-3Zm_XqK_w9x?6fW@2SIV@R6M#h;r9{rYj2* zrtcAvY1?@ZH=%S-y=7zT@O1Rj;e$?#M!joR+2O}+7wfCj=MAqZPjv6y~UGQC%pYv6^Ci!iJd20-ZGhLk`9(xWY;oarSY zxIOi0(aNnm93uDsBZwRvz0}JLfg^1fgoH{gYYca`B}2fg@K3f^1`agxa97Ss4QXC3 z;7eVTreGoCZafWarB-%;)Jl*XBpjp6ADh2{Vuy`zsX$4j20!7Uy~2c`L^(HA`j=g8o5 zyg0|59xpME!uO2iDS`Aonk^bT;=1LZJWqy252R?@$@7a5RO?9S17LmS_yl}Ve|ayw%ZpXLR5OR1w=$}>>-NU#aT%AkfrPcaf6_}%oY8q zIor>&XVR8%=g4m(CRh)m8Hnl+&gz5c;lD;TNuV_m* zyE(9L4yZN9I*hPX3FIT!qw7lP6H129DgjNORZ-1!@??85P*^91=cDhcCSiO~|1H#C zgxW+C+*D-qn{4O+H2I4s4DCtt0v4Cr^lw4uuu`)zA$TExpW?;>{P0zT?eZe$oYrJ(z&Wk{RLX8@0^}waE13!dJ77EE)Iee6 z9bNO&2WIzh{H5VGIdEYW#3HEHFOs}b0;Jo8!6c7F=S@yqW*x7vbq;s_Ytu4 zWJc7rz5n(zZsDnlYE_X;JhS;VV*+cE0v))hwp|!WD-G%dy`hm@q}(*%+gnIe)f@cO z6g2$wfmP{Ig^4c#>`ihczQpYNE)YtWn1j26)g_o-8SmqCjSGBUTfha@^w{w_@*RIR zilWsG`naxdJoQt)STp{K>+4 zTy!AZ$<6}&*n?4N`p!D#`|FS_7PLF#gXv5Bleo6WAOmHMZP z$`SL}JV6xn&c^(enBnkdZzP3?Hv!a`&rh-am=n}juj;KF2y_+96HEt{1*~aHOtZ^8 zZ?FT+CvzOW4RFXo#C#L0#oX<82tJuu*P9C*`ZZ$--fe7C4rLT=kFc{TCxoe7l`HpA zfPv@dSo{Xf~$kf*DE+tXW9TD8w{f9ti?-&w5`qMY~I*V z9^!i0u1^V4QqS4oGr%MqUXS`=?j2`wI5q%b0F7j~{l?)<8)Azlz`XHP=!7=ca$G!h zTl5yeTnT{GdA`q`|MAqsv9i70LY)3-Q_qt7ed&ajeNx5Q2W95Ej{d;SI|W4V za}ycz&0P0UyiFCMM-`JHo%!hZ;BY*=r+Ldq^4D?}X_o3@wz3s4toQ~__Bp;a7n2ls zHq05N*`ttXW>?qp97KYseG@fW(zdmJUf;0gSxosAYX;JAof__o7)3+$lI!?rnIr!9 z9{$7_u|HbR3*czIdCi5gF*SDciL|d}%h+F%ts zC{Me9rUROsmE{QDt=F+L) zGM`?)F7vT>FvMwmMZpm^L7J;BfYwFO_Nr-s8$)_<=M;CcQz}!VD}nMd(d5rHUCpbT zTJnq2&p7j(=Anfpbr--zx|R!V)E46GVtyfRqZ(xkg||_ukiCei>pa~{#pTpJR}9^* zL`x6dMrE!Os>AifVGme7a`#g8C4eF6TF@cMaR}9#^M4)?2Cdw7b3aJF$yHPu>NyqH zYy5`*N-V>WnSVW1&R)s;$-irDU8EUPbG7b)EHQhGl~~c_xcUqp z9g6GCkNzzgFs{J8Q%n9M8Asye6fiK%F02UT{|zpm=1i*Gxh#CJ-SB{BejVW~H9{jOu@OLVgsVE78SGwSn3=qkO87_En zxeHEl!M_Ko;|K=x?ERt{^Q>KkM9gTr14b9zv^$)jCfT{PEizvP&dfC@*bW>POiD$n zEPZ6SG*Q8x;Ic@Mh3$B}&J9B$&3ruwWH%xO(RU%rKrjYTveif>=kt=O_>ac-BI?gR zqkOsd0K@g$0NP$G6vU+T>^eh|IRXURoMf|7~E6#Gu7BV0IkV_#y#diEv<)=xSIZ3xm8(`{Q`TH z_AiGiE>p2QV1{M(P)FOkc6?5x>(;N`522P@$&%H38j|Ctn=9WKluvdjKQSyne3mAo z*MCF>6I6j-=AnI=0#}KVnFszr*sbGt0JwJR_y@jH?%@nn=+^LCOSe&}L!;S)fx}K8 zHwBga-kf?|2YX^}e&A#pq{^Egf`h*4t*mFsMlU%;1FL)t@N^W|J*RKTWI6O4Ubhz&Dl(5Fr^Jj{& zomGg~9&07=W{gCk*lzbKKv(bbZWMa_{8JI9L%W`Z}L7h9cuah}rf3mmTVEA2g#sr}~oHM$y!I!g6tz%OL z1{x1yBur!gG($&|J5%ui0|Z)`rN2g)gqIewGQZJs%z@qaE9Jv$?8Vg1qFj8`>3o3& znH7&5zuxi6c8tpJ>pe9uzpr_Z1#~}wb{*aE`I7PY@4pGAhrq{)4W-8f+h2ZWVY}gA z2ipO_=9~-cmQddEfEB(;p$mp-jI#m?N=8tDf#LNA{ZV9nCs1A16F{+vxDUo)2 zeZ7ggf-3{D{g4WBPj!C>z+YVOJw~x`b%P5Y+s_4Ox!_Yl>IDkAEum+HV`RKF=tj&` zyF-=R5}H5|sys>4qKaI=%-myb2aZGFsHOhY6;j1&lBXg*G$%zVoBystC=9D5Xx6I| zPw(ymWU*O5m%XsW?F8Z^%pEUfv>+!Yape-JWyjhUJ+34&}%?qo{Bv!l&5wd<|;wa zn=DzB04tB({h4fCW^ZXXf0>h9y>ABE!W&n&tJh1s0M^^pU9P$xB3pK9^MWs}>MYY$VeEMjxzxR9%2`7rOe% zfMazobN^2|s#?u~s-=Q;7C#(uz>uJA)@2{4(&5qRvGmj;-0-Y+_F0uTwFm0jlXgk7 zXtD(bBZxAV`hyky|2$ zwm(%`9Mi#Uvwo_kF&s2X6j{SkZ>&Z^xiPF*)}H7kPXOgIAvOS=y@Xe!+O;jLycf5+`3JHn20c9{%v-^L9z(4|q^cFAV z)1R73s=H9>ZiDo6iV`uNFgY-A{4);gj?7-`}K6t$lA(8gc&nn|k*_?@zk>k!sX{-rr<} zG(m0Iw-9>)#c^G#nn#Qkk5;A5sR90Dp{?cx83us#I4qUvOYn#oQDQ~}N#lVjQ|(^g zj7tUDco6rZ+YjE}{0%MV`rAy!$gl6}^5pz3d$xkSEKetxpzlhzd2r{d!F+9 zy@as(J)d6**zebG{66arNNnWY%>|T8Z0yD5Pa2y6*&2@k8Q`ggpVo!?;rvjJykwo7LZ$WaU3sH>jP0t%qShEZ8e8o=|+YfV)0Z-_96y!VDHu|l}qf! z%ub%W@CaJ6Vc5KNC4IKE%N|CwE7v|=5{I66jrBB|J&s%!)~xn~6`!mk3R1v|FzAkA z`1J9xVl!u^Oqc4^CH7TLo`k&+*RT=r#>eu8)D@fZS8+>)#dG+6qlI*{?{eByOQCQ$0p@!eQoMHy zP}QkP_AT2V11dZUQzYC(L1<_hZ5q<5xF82hYdCPN`j}q^7O?(u| zz71E0FHuTH^LSvZIfhK?Om=^oau0d#z*l?zl`n)g%W8PWqNYGzUv~S9Iju6D;>Pgh zpk!~29)eS%KE~bB98Qu6OVxbIx+xn>$Y+CXHf4idT7K-t)eq$Z%{i5o@T{NuQv6Ui zI#aWt`Ta~J?U9v6_?4wdlW2Wq2HQ4811D!B@VT; z;nAB<&81JeskD@-6H^#wVdp`TZQ-+hsRKp%KuTInWT#P&n_{h6q1k|quqg9~!!zr* z6N|jbqjS^)ju|Uxh#FrKw`8HIMBR43{+(LT!-r$Dq=K>ekt~H2MpyYR)nfqx}vhY zi`&OTiLh|TI7CzAHGZ~UePyqojX*Q?35O9aue`>Op-A>E(q*5?sVyxZD-WuQ6}6-2 za8A{A_>h_sRDOMa<=f{~p3_tV&BbfHRE?cWe(T^BOOGkY{)Z^rQL}!i?c=@9FRouF zeWZVEzf3O5iqFg=7AF;VmbgXVz{>zR)$AT)n9gGi_DoKnU(F4c(MQvXs>Dc0!Q&48 z#5c_u)oQO|*8^Pn4VUJXw~slXv4Lck;{C%q?gr=1Rpbs~pMHUNmJ_{NceSeN@V!w@ ze~J;*A)iT9j%jQ*v-fy0R)*HPp>~UTx!Yo{-asvQ2Dj9&iYYHE|00#dBw%+k8mn^V z$kk>#f$Y}ulTIwRk8{p#d1@gX3;)B(a+GV$?#EZvBEDL)zheI9TXi;5v3G#GZyIcHFd|W@AGzS)gW$hiup|gRr=TvCXf{!5)w=rWn`Aa=m8l(j zC;{S1K`*n%p~kaxt#~9TK3l~#oE#$sV@j_mn3pQGMK+w})a!H>_{IgaIoURuvom^$ zS;AZ3LsL16J*tX(_5}5ZlCR}z2plyj#~l?x4|i1!nTqngg8sYU(M%2M$2T)=>cSxS zkqaIi1plp|>+m7en%y4!f?8N9doP+wrT6d`=4j!U#re6Ux%ExknZf`6yz7D^t=!Tv zN6SD`1I}hOuvY8 zE1{ofXr{wYvrq3kt$e9F&dLRyOq7i-@%1-#V@;o1x-Q-kcTOBm@oEy?s@&CwVEe}d7~+zPH>^xy2}ID4Oqk^I3)tQ+Y~ zXKn(QT!Fv}+ys8c5Ttk{VEzAGq`~S;fzz#@K$|JB)>Kn4GzISB3NGK>wR@KeVhY^F z1#fb}*C2<++f^?3`<-0yVi#N#q@JUoGX*ZS7|gj zk8o}e_iuElAygAUpP=!DQ8noYsC= z?vQCW0-$mVXxAcIZolTIM_3%L52{#sA>0y}ymO}Vf3V3Si;o6Oo<-)zJ?Pe@%QUV! zwSUXrR3s83IcL&VXm79*yxGz2JWZ{@JC=6o_CEriiMimcfZq;xL4QY=`9K#uJ4oG~ zV9@PfQET94S*h{GImYa0+cfiz+fTZJV6d(YoeD8YHqkXd6MBW9%v#grRn|+y3_e-? zch*TyIA+LUX>vmr&q48|*=n#uWI(tpF8J=D`5!z3!=d?zqr)$QQQ}j4^F#A4xfBLg z`b%8FzQI`ZR4`LZ^hW6{4%x5F(8?hdL;QeDVPDtxbSjN@aam%NpkRUGBej`QR6fe?-j zAeaPZYJH5X1?JBH?<{%Fq^U@WMJw_`n_5+3Chm-7_^f!z?SaD21Uq?9btMJtYRnqm zl(LE#1xoPZiRlb*osJis7Olx9ND1v02U?dsT9`~E`A?2ckEc&(+_N=}V`7?SbTYLY z@KsTnmD8yCUwe%;NNM2>D@Hq8uB!+n2qbs;lD(R6m4dpOaN1!)cs2h!V>w9BqN*g| zLlrX*{9HVBx@~|JfV3=Zjm(*Wi_o>`eWTDYOOy!!j7pXiVwRnH~2xUys!QodQw@oy`56jW;m_RaLo`ER(e?sWo@3a~#n?y*itW zJ&^@UwvnvnZs8_V(Ldnne&>@lU(Q!rvOAw^E9V;!6edEpELXO%W)(86Z`rZa97ipx zQ8Tz%sX6@y43ht=z%3M#_3g z-PIOP&9%sL9$;@H8=ME2jGk;yZ#AnLS7zCXQ)ibr7qD<_q(%+oC}t(+HT)Q++hD9* z_Em?m60cyaOd&R~FIj%mc80IzB)+t;^MA{J8xEBc#wvzR*SaBluD$+|mKSY(u0xq#N$YO*vF#}U4eu=z&ua6GRU5v~kn@tVSxWA#g5-AC6{zuY zVl`P!LYMGIv zbZH{zyW&>|s>>NF4&&Qo1hYMp8{RkX4(K8kPyLZF&L&#m+8x4L&pj}~JqCGl796Yl zc`C{a+Xx7izjT@KmbY)z=FihM5goS&pt9oU{#c?B}1Sn!yIWv)`@K~a|s>p4nQfhKgiqGj`1$@uq+hz;U;FUQv zcx7|!8wT+~tAopbRhMSiKIP(H$8q-H1Ex1^Ne(oFe--K(!=n?(UO=6tRPJYVR4v8Q zln>vk4Voju{WL#)1RKKZ%vGO=BIq?(1jG5CiDRAl>>aTOa@mT!J-SgNyjZ!YNFXsE z*2?TwsRo6E+4?2ISA=OkSOQsP<%O3M9=3*Bgi4{b^Kmgw4Qnrxi%04X&dJZ<9-0r; zg-&Jgn<^udj_0nfYK`L__r*audlct438?u>D+kj+L{pU%8>puG<;i(3Ic1CO3=G@x znT6F(hN@#o(O8CwP)}N5)WgODh0b#+g_^t~W=x7x=QXq}8`M~mEEd<$Se&z)6TIXR zw%ux`*JbD&!=r1_pSTsNTKHorJUqHV=pIN6JkE-1dLhF6wCGqb`D%npwD1j*Y$qGZ zLkBDADEl05wc*p9AMuh8lEE7I+MOXGYyF!l<2Sx;@d?G=^2`2hZtuUjex_YY(9g4H z<@K|cq_7K*J3d)sP9ieP6|=#*C3o+OPPE&@`N6!Ka5vC2eofZwE+hu)9L}ErrPp`} zq9c1Oe?mR?ed%5`e(D1={a&#u*g^?FzXnS}mzTT_Jr4qU$!Dl8hW%p{m+>*%_qF?` z*6PXn$8t84y|sLD#f27%P^qQ<4YG?-;j?KNQ`hL$x=g0@+_txAgvy-x_DD zuQ`89M@15Abm9EF7(^tBxY}aMvc%Vbc0bLVD}=aA#q@35>{K{Ks!+5=b*fUSW3>9egAVGC7Kq#lZWR#!G@HvY4WKreY)IIdT? zd#pwTWc`eLtjI01LLfiZ>pHfF`sV1m#@tC@lM(hO(ZPz5dc??Qb^RS2VSBr8u|4F7 zYY#bM+pDID&gIB~qyQnETPNH69>-jV;bkz=SvNpxN1Ru#t5V17nQPBJJd_hNpOk^X zK!RY2FA~NYUxYm+#%r`@nc$=D!qJ7&X_hE^Cek8*Du`p}yZot{#crvag*2sE8F!6} zkawUWgypjJtZ>d`&nUYWU)rb9IAZXyPWpz}`TPm+lvqLhXLzXtP&Uh-4)MbjFK@UI zuUlygtft8J{U+DK*PHg&GmV7#)q zR)fh763l(TGR$^^=a}vK$zUmeRi;Z4+F9Q|*a~fTD%rA}m~me8TIo&Dy@}?P(!hwU z6$lzLE>$VEy9%;%90U*Amc8D!CE0Pbu1Td26N-5bJu-0WZZ$umk`7!~cX2+)9boZm z`Md5CJ2$TA$lGtjIh*j>zYf?OuPt&Zfh@n3ZK@nI-9}Q#OgUM84UyTttaf4{HO~gy zEmyW7y=QiU2*G;fg#)R8^+*%pkoU4P!JSD_OK#1OZ|}CwRvg+*J114?EQL5(4Vm_6 z4{7*5Qi6;Bp2GVSHSr(%VFi``O!XfTod?AnQ)<83TsJ_ooC(WVH4(+W`duKCY z+Xm~Iu2sJj5^@A=G&tK((wn|ksFoXmZI1az?l=QUZ*>=<|)KcEgJ+TD)H zys7?yM6dB$D$d@|A2g z;_Lu|ft|szsJFPf&~B2M9ia&Vpdz>?6jWJv((rC31`8Vg5k=-aWHS`1>mO$@j7q&1^xJy!Fi(nowOZH~vSL`AXn1 zv()Vz&;xyuLZ-Vd&{DsqBWIknua%8EGMCW-+$DCx$wz*W13O*M#ujaW0Q{S2?OvGd zZnE>RU%nZsylyE-2BK2sF-dPXTN|;{72hxY%l+fXO5_hEneNNdd|GhFH)wT5X^e)7U7kD-nOCaabFs+-nKberH@cvLA5HLada$j>4(!%O!5R5 zD?cK1xxrk!a!d?);6Y@VRKI6H;*<6Uv}BDHXnndoqOMhZ`Y=CUSO_*tY9Y&Z9v0zJd&pXAWUR4>$9L-{#F*_6pR5{LLvaK& z=YQ$$4ZRA;?3~oey`f@AB&>5F2o_Sq_s$87)#fk));8SiRy#E4CI13=YLtbWJWpu# zK&!XA_PTGWJ@V%o3|DS1lt@6rFR{#Un2NRAcCtdd#oNO+kq&AdBq90Im7$RQjEy3$ z%aV{8?S$lCi3x<{YI6}K!+FvvT_1-xyMWbnsif|1c90^c<$9{?JEhgKn5a})kYfd> z`4J)AU*LEr&ki&jHpB!Z7R$Zn`AG$ zDzF4au7L@yUCz3OEN~VbR`1Gd_Cac6qAH<0+SR+XX4VM6OvT#~jFp)ov%6YlNtPwC zjyZKa`hsp<(3<1HeQeh8-JL>JO&TTN4^|{UCK&8;9muElMY#J__h37-600lOKm><= zSJ_G(hbp{QVaBO7ngrj4z^Q+Vm+dF~zSDi$_HQ ze&(uZfd-k#Vs~sZ+@MDW?LgRHjH5DA*};SDkxvXm51b$iF1A(ea|p$X=Ejn8Y(Un& zBe_DH{t3p}dEYnYecu^=LkH-TEL<68q9Q5kVSH`y-DW_#&dM8wCy-_` zh|MH3NCa}CDcf*PGE5**rc!|pA=GL-bzm&hZ_MuZ!)vnPm(`R{zC!oLuK}I2fz+j0{=#<@6I{%$h6+$Gq zcTN>>>RW{}rsvr0ELFZTsC*ZtYx2Gg5h-^pgh-GY*0QRtmNmQO58?Yz)YYdVAxPxA z#YTvHSsi$>a1V$6%1|wltIG+rH&9H3`BWffDjL>(V~q;uoum=Hz`hAIJa~Qqt3-7{ zU^2JQ7GW~i;dFpD!=Mz!Px{ssKUBp5#wIfY?Y38FGIx7m;OihdP_?j?e_1(7O5SS` z`yApe`>|x)Y#Tm9;mch(v>W~BOX9jP&Yc|0*_lpK{0008^c7$jD}2GU^C&O~ zMROO+gpqEIz^xfE!z`lFjfn~0L=DHWVA_t$3E{_NeV9^p6*-Pc@k&3k*1JvBFa3-DL1ukMGw zMZQ87r|j}oAH<(A2RE4>i{I&{3)3b(uXhyu%V$5TGRZT!@+g|So@N%KldOStU|GlK&?LYvu>6{=3%hWd%m z-7TrNg5`pje3t&EhuI~=Xt!ke5&CABsW=-YWXDCg1mIY859Js%m1}I?gyqB%=%if8 z|4-WT(^70VLHml!g!aEHd%EI20)FbyDEF*#Fo`(P>ox)UOW4(1xeLkMq4ViCJUoq< zCU3Kej8)M-@8jM{4X@~j*fnGgrqOp#fUVD_qd7o(W}#y1yl$*w=69tYF>vkU;E~G+ zLT80YWTS?on^`gb6x-41xQ^gVol<0qnMa}R_+J+1T4iq_Fe$wC=1bV!EZZ6Q@B17e z=JKaaMV5Z7ODxO_L?fbmjC1#GAri)AXW|aumeqb)eLA?W^z$*Pje3mW7FuhkOy5WZ zkD<@jI7=-xHwu71>(Ad6!(gc%@FZJ~zogH7#;pCO^-&!b*Hu~Xn>pl240#C;s4inq zbT$Mg3Z`rZp2t&P-%gf3X8| z6Xg3Fh&79L(pqOt(#5Dva3XM9xI}%)G{p} zb(cyy^8D?p5d}E0A>v&<3C2il*b3QXKYDP0iVxFYHKq644j{u5;B!BLWy)T4I@@Y) zc<9^osdq|b-=A#MQwdl#%q$9VHz=Qi>qv9C%qK-koS+TwRrTI(J9G zQJgh3#}IEW2XSOSm|bz@?J-8&uU%qh?uePUE2$8olB}|2j+N!DhUIBp3G=A{BRO%XPs!&{@E+yiUkpw0>QQUC1xS>=wAwk@OnE|K6SnASht6ynd zYOAfbQbj}!iv+bQU|p(35Vz+sAZS%^gWvmeo@JIyT&jQkd`V{Jxyy6zxo5lQo_j76 zqf6W-=}Btc;~VMhO!p*y<&N(py2&4(6rTuP4yC{if@&%)G9q8fT(Ogc;9RU zMJ|nuWoB0iOf<{{EhR>dO3kZk=7EGsD70Bh@JPQ~?bAzfM@j`xj3cF&@|@o@s#fvE z`LXm1R4xJvSCvg$L#i4O!H>GZ`QvSUyks*Go#3oD)OoS(mW3vQ^`K-sB(*7VmQz{} zH?lsro!*7Ia|%8r(=(oQ+Q)o4J4 z+SMb6Ml-@O$h!93oJVtNNmfvE4mDM4NGyF4L#qphHhH3#4MWK=>MFa2?`xc^38%3$ z4k1p44ki^%bnb*h zxDd1Jk)y{QOS9uk7a!TZQZA!PWKIul(N{(uYQj^7#*IvS3zI_|M)mSm)?7QlDtcv! zHz`}(rLw+-CLFhdm*018#B`J8i?fXs@J!P70ws4|ufYOS%BKv%!zS037|2#Ki0hGr z@}{jd=qAW^r6b=t-&wP)bnfPRx*JDNQ{CMELxyGN!=L|mgbe5QNRKYH)%_#Ce4P5O z&RflF`K&wnZH41*9**yN&v1OG?@xAK+om|4U*NQP^pPAqd;I0$`EI|Fw>a_fTo#$O zA7hNpsoYz59&+}WG2r>gQWlk!EEKxer>+Fwb)5J;e*gpP%RMyp)r%t2Pvxd@c?gp? zOD^kHg=vd9U*Fba@5<9ba$RMqkK+ND*Eu*|=iDzaa&deX?Y<7jzwIfL==|YCG;`4J z?Z%z`^?p`#k!lGpUDMQ_9&BVe(FJ9juj1fqO&8~0n<51-iDv`Wk4$@0UPPfwR7n~d z5hXPFymMJ64Z1SyN6uu(h%Od2s_#wkW6V~_?=7Eko5ahAgkb*OhB4uhLxTC+rIYS& z0G>JF^!fRDG<(lF4jAYlOv@wF2I$K7Iq}hr=TAI)%pr@(fJV+V0k#EMT9KMHkc4wD zjWG^cFd_;6im-KV;fcp^l|F-9z^DotWRg~0ISk?|9pNbGla4p!jE^f{^Ts#07_dnG z6L1)JGQRR$5KSNGR-c*xy3s}?`Rw~5E9G41oD=ohT++H1dw+*kM+-3%#xfCc@R5=yE)c%2i9bq!wbrCD^Y7}-;I+Xa&4S*NmC6QB{F zMjtt^pP=bGdHiUgIVuZHOaHF!Y0~K?_eiyoluIWdziDq7nhp!-(|NSK_S?iiy$)4!Q1xPY!8zaYO*4`Z|H;mOGK&n)VT z9p&Rdva^qR&Lzr6DGqJeZ7Ah#lHv3bjm;AmeUeZdA`zWn~t zoB8~GdXM7x-JZ8($F&kH za|cA0H79&)-5bv-#hQ?eP1obGt5fD2Zpqk2k2|kY$=Faxy*O}G?o{|x=Fvk|6)TDE zoWo7CdPM175c-kx?4|L%*z6@vdTn-x@@HmV`LRLyWI3JhGb87&_vbG$QhuaJN zmbJ${dn^*PzYgoreVjjGd$ZTpmHF>Kn8xmT{K+qWJ}Cc0@2_GI`Q`TqHxw_=ibr+27H={4cBQIO7m@j zv=!%T+Y8p`k<`1+JB6JdWUWkmqE2ze7tyxMx{A`8M#?}=5;cV8b*I`a)Z%WS0}S|s z?nTr()9KzFMv?YoO*c6w(!d>VwVOr9y1KGbz^8p9wd7qfj*I#NZ8cWjrDHiZtXmKA zLVGQsAK|}(`oe#u@PCYlfBC&GWccorU|aLy!&&tjaIWkIoIiz3vi7~5_;}ZOQYD+| z0_kG=SqyFZN}Ak90T(+~f^=6oDuVtVM2QxjTtM*!LX4?nlC#e7Z?d93oi6n252l@Z_6X*|o<8zTInu_bmcjA1(q#?K5epW|# z#jU&AdE0v|i&`@vS(I!95o+#Z=x@T%V}A3Il$rh#KKu$ZRpC~vu*jXpq?s zp-HDHw^-zHB>wVYRsCm`Bkyt~X1)JOrp_kM3LFzi{cfBQL;e6-J$HPL|u6`vQ zAv1hpHfr!q>S1#Uv7crPSl*%cksC%YluyxQuB6R-(90rxWgWasbQx&9DFzD|=vY2q`u z`z&;97tsU%yjLjPL+~vXUPJhDkhuQeRQaNdM{kMj{Le*P3q_Bsi~msWPn7#SaEJgf z8dUy(P%tm2d@rP8kT-FY3U5~-F^aEK;IEGY{4IKw@~bI#Pv_0K@V)2s<#9s^d)@AB z%!p?f1%v()b?ppOyG3BpxouPTz|-M5JiLqxEUlv_;=qV8%i1j=knD3jj8mY@9M1=l zAUqh@+SWr9>%GT0eRFpP+|dqwq4V`y-(pR$?_(H9u{jOn&ubcU{vm9vnexLS(Lns# zSO`m7T|^N-8%cBl)fJ+WMN0x7~vtGGWRp00$i0*l+=c=OBRaOnI% z^f3>P#e(CNm3d1i|7Ti+h@k^hd`7d|TJy6UR5w%8H@(r%PW3HAD=|cSl0EOlstVr= zeb7)SO-|;)hihf>Z2Fx*Kz6M?&m%SB*L!>M>$aIJciHowb?1!WU|kS`f2P{z zbt`pNgrE8YL8tTj(&t4Q;Sm`J8ZCXs*<&XiQi(_8^uQzH+(8qIWNYAnN)0Nlq556# zx;N)qiY)s@kNWggmBAruO+S+!t6mV9K0^I!>2)Pv6xz^G{Y50PH6ZGIH`{LQ?Zafb1{w)%j`FaHl)zHYqp%ds z*{F>qDWx25Rd0=v@I;;l+Yc6-m;gZ8$?rKEH!@Kr<0{?%W5toDXGJg2!A<~-;Ev3= zO7!yAPU&SG9a+71RIeA)4n2?o@;&E8N<1@Qv2U-LXq^7GnZO$Y!u?1l!=I=NyScAW zruKpAesC#;GqN4Ix|%-N$^KHt?|2H`e`73U?SZ-Y>X3s6J-dwZV^#i$zz^-2;LS^3 zqMit$^LNKztRvQD~42JkN#_)Nt%l{T~;4e*;?=hI_iP^zr4WrGHr9=}%*+jj`0R735*>Z80&U$lso= zt40=o66`_g=9Rv&^lxSf=jlJ%HZy)EfJKwWt5-)7>!EU!ci}OPM|o18PO3?L`Y&D+ zppI2LBm%YC%@om{9a@bfR$Il}XYtXHepvN&nruiQAA|E`wc;wal1(z4YyyrzVOdws z79TM^ZP^2D|MdIOiLveK(+}UHKD}dRum4vwdHc2A%bHcd%l1L81n>C>5S_iHBoINl zy}nMbY7q1)1@HHIt@U~}=Mjf#<;7pkhoF!Ba}G7+{FlMIil$ zAZ3B8Sm*<-&)Wnkm6}yRY-NAe-~RcVa|P&ClJ7X>=zb$rR7dAmz50Hq0@Uwx<%b&n zl>mxs%K%-$6CcoZPOFNu2V6LxcAkV?9{?5Q;aQxe`BC`$?%o4D-|1!#YQnfVfWXn0 z%jm^QW9gd(ek^@YvpxWSp!^3u{K^XBJTA#x2*2OhYsDbEcWDUeW^XX|E@5e#?7OB^ zaPXm0nI|X17Jaj#*ZuT^?%DmI@TArqh!uCaWQ);n`24?mULl!f&kZ`A-)rHK`}>a1 z`$LO%w^9CE1FkZD@()I6GZ(eOF`Y8!Ux>EmN9TZqE=^m}wA=%DVLW}W;WK^jEQ%Jl zSPcaY)OhgdSb!Y|5dK&kTCBh@ECGKO-*rA)Z{%k5pyOE2#=O21X1iT1{h)Zpr-$dM z8c*M0NKM~sv__ya$AN2@HJhi#@y^abnmb3@*&~j~st0|FEc`w%*Xz@l_h!GID8`kM z#2#YSaDeVbl*4c8(at#;{!XP%7T*@rdO45UZ~kX=ghlYjFmUXAi7gJNu90C}OM=eY z^yK{Llim~zZ1qCg2tkp-USm;V_$Hk3$l==5`?Xy6reyPi_8aeBOAg*L-!!T>B`oax%-hTk6>KHAFZ{nGnlKN`WM0Z0_kz9f+ zBTH-ex4?4RpMc06UcuS2xyl90uF(V%A?r<}kfb%*I&Zv8;-2(H?>L`+62QUTpP91e zXTR9YBBL>uS`BXoAe09nRL~(H!~+o4N2V{~5g@QtZXnE$Y|A3{cmpBl)!uDOabDIu z&k{p)Y2xyagatc?Iwq2=kl>xVNC+R;8gNxMoZ>A z%U-qU_C7DB9A>bXb4ML{B6t2;8n9HmP|ngR=FVROzdMic+@e2>FN|*2H2tQ1cD{Vr z9(r}7AstIQPPhA@eLB!qeT&W$#`?XWi`u5dQwtvHt>4W1g-+=@y&OWac`bRtor4?_ zpO7Y|df6o+CHnKtW^!YST?bhrBZi zD`X^B-m&*eK)Lf9lx3E$Cy1|al+w+}O!1g|wtmBMTr`@vLs(Crgk@7V;E^&xOTHUs zVTz>1NEtUZ>Nn<$4;wX31j0{%kcj>pX^zhoflw20qxi<8C=a!&p!*qBlBb?nfG1aY zVxem=k0us;p7h$9M3~UUW6OV_*kH@P>MN35$=`vk9=#DlK8UAqXmF5JH~7GzLVLiU zw%}-{6@W6OpF~^MlsNP0Q@J^7`>eu6-Tn>ID>QD4272yQjVlx`7inO{OTsrl`T{q;+I%jQjy8(R~tjVsgKm7D3soN)tT zrgW(m)rnnUBzk2bMVOXia1Ru_bmytxUUEdTTMR0Zh8(fM%WUXWjvR|CH+J7)7s=TRDakQ*Exj>y9*+&tan7@*QYq>x+0!>LHXPukw&%#Vc6t3ycy>a2cSQ1 zdMlP{SGE{L=2{({sZnPWo$ z`YZ|F|5Lqx4u8;6HsXd%6_%D#p7X0?+Rs?%pNm~>eiuQTF-Ojga z@*>G;RQBbFgh48bV6BsEv#EN#ui#46RXC5sV`N9*#NS$_W;&C6s%8?@@Tx_p?)>3x@jKsS=Jb}_q1=vW)$(9~EEi^3XLj6-$+Bif(Z%4&!y3o!sqcXk^}yN3 z2j@@^93ktUfnE`rCtjk%hU8Q6NbOUW<1g#)@$jj5Pfqp^Dd*1)C8Va*GMHz4GC_i4 zRAkcvE~Mx9)Ac;hw&y6c#0H($N9vv;;B^RmoHrK6(%}N4&)7JDNF}BZ#}?h@iMU z+@YlTZgNQU5tpafgwf)LVJPnff=WT_{GHnuM`F`Af+{t4)dVAmY_FfITGp3h92Q10Bk+T#Rt5(a9r zm0|TZZiplUNKftXxQ26rzs1NXX&Hs7uv_1 z%F_=l9hw|>2(`RUNouv(ecIktBUO{_x6SXDNq&i}g3uy~QGI#SS-;9(X6wDGABl(u zK4nP)N?fwHZcaB;XdRe*%9G^Fz#%!r%ANhu*kb$o^vV1?qQxzd%v@kow2--s%9+T~ zV-&?wfz*&GCCY%^ni^kd+J1s53p?g74P=t@878f+&GY0{^jRV2i@!8ZSPo_Ivj<<&{ znkUBy`oE?^S<2lAPI9l#5q_P$aBHhhrPUcioy(z#qAHptiY`*KtTLttMj4x2TF#=X zL|7_$+1Jh-N%8bawx66C1aoB7kUkmd3Gjo}k1UE|$dYKua(dCcJ?)>jFbc?FQXh^v zV8Ls@V*p3vmC=%=2E#_FfEuO*yd~Cq_-csM&3E78djVAbY-Yjg<8`}Ib1JZuH7pC( zQ)V}+Uu9iq9<1L7_^wDGGCsgA?`9RDXdzI9jVQufUKjiG%~yo7k2)yA3Rrb&zDb@iJCw-lI>yK~hZ-9xDK~kwPDc71>#CzOKFuYpgcmj`d z=*1xA@Rd65Yr@)B*@AT4ib6;c!0MW{y$)=NVognJp%{|K>a`K zFbk?61XgOQ*BX8Wpq?X87nyKshM#=8zXuT*(ETq(_cJ|F#_onU54B6&$MihpcaL8t zyr`nspq06)u&#x({<#`nYyM`q=tv1T#ks`<33&%3{Lwl7w$5=w!Syt@RmdpH-Idq*=g1%NQKJOD`9iXs9v8Y%gafKCkt8sB@O`p z>%UZJVg5|39hKj%u1A-~nppKKbLgo{JW{txoy^laS$WBt4ZVa-m*aoX2sk|#7?r+# z&dzOn8~l=g4!hFKI-UNTAP?sg+CIl~!uO?fyze9Bgd47>mGZoBmP|)^FT|R%fetXo zsdE+3IQ6dv&ZuDj>VLxT#Qzw6e>XA2@646o3cnXqJs-bMkaf|=Z=(wH@cZ{6#qfKE z-uXS_xAO5UekX9VxUia!-(SN3hF{`;a`C%6&^YcZh4{@UU2nPcRiwMqYlhm97l)`l zNHVLCbg$LRi<0h+RL`N?ce8liRV8_NJ?%*FI;fCtOHD`?(yh)m3;d977m8k={~CH- zIxmaQXC5hr&!PYI@oBr^pxdX{=ip)ImY2UPKHL6n_-vjK;`410?>EQiVyfrh^QT#S z{!=A+_}nsD44R4z!p4s>Pv4&ZE$4*+bTZq|TcS@;asNO)H=JYmV{PgxZ+c<^TeP0ZWr8pnG z_j0L9V>Djva|9 zyzwva?Codfsjjcb>P}ERs%y@ekX{c$W_Q5qSMWd8bLe#gcg^|;-Bl%d^m^Li#ptzE zFYJKO|BYS)9VLDBGWJR!{=|*%5n}g!T z@VOsQIG-;4uK3&=3J0IBV6OS5Y8^8ATi|mqs^{QySXO}Uqmn#){+yH^9rb-Lz3_X& z=PDnE;`bwp^7{zRz!j65ufwR$pPnzo=l^7%E?;Iyz2Slosq00yg+%%l_CMA0?bCHx zz5g#2=HYd_YVg|8{?|J@;PpG&|GrHs{BC=HF?#(3r<&nczF3Ld!2Smsr~bL`1-}y^ zdx-X#Q6YY3ij2QGelMnaK7J!vx!S10JpBIsr^WDlhTi!--evLj3;k_SM$#0`}GG=NXb^U-d_NcR+II_Ek@+7ur{SvuNI$;(Rn8 z%@t4`?W>Q)OdZht9qp?*4`lItz@LiYc|Q)P7sK;Ifa6Sk20Z`o{(-+g%aAg=Ej%}* z-=(7HZ%)5|qk2C5{w#~w*(%JV-wzKgM!&b}ogEPS-Q>eaUnYotmldVompG+pc zzh?oB)9>j*`u(5e@A0q!_`Ttr5Wf>3=5K-DDy1nYuR{#cA|C*w3U{O*vC-%W^K=cy%y`2FAI!&>GF`EdBzp?uhiS9Cyf=kj3< zTu>+mAZ=Zbpz79J(e_B+CU(Oda$OA6t z%s-X(j`N-!{WJ0OJWE8an5DcpLiwI?h|g!<$V|8T3`4lHTJ&8=&3_R*p%+|vz?*p@ zyJMlM6OSF;{8PDU*v}yD<$YY-Ix5(J`zGDRS!w=pJ#=PM{j^i_XJGxl%)n5x%I>jW zk1nD6MxFm!op+dpD~q~synR*qZ=Te?Ql{);0=FJl+R`U1f1cc4TWc7Yy_$i4{yvw& z6&2}aHC1^fVg==)H;vjb+ooBZhF>pWriUw7qVH^AxS6+V3j6XCBvHX)Lo zr{;H{?}pU#O5@g$deZYh#!}A~CGlcp{T|Z#y7i4ncIR9}$XQP^6B?Ec-y(Pat&sIa)squD4%-7O==)%js_ zh~$S=GY`oh?*)>F6z*EwE&w_p`KscZ!(M!dI0nl&dNg@3G2iGwN1MYd~INBeQMXLf|GGxynE`uajpp?)M_o!r9@!u z$yCMDoajhz)yIjZh(+Aoc$#-OfmG(Tsr6n=+8IxRgaZ^Z+t{dZtH#~q)t3{5B5JBD zc}@otqJ7@i7OqEMv6fE>bDl@3#oxMIC24UgegsfK$DH;iC}|f(|1>tmS}rf|(zqmA z@{)xkz2E+V@QLk--S{cNKYi$`A=ygY=PLtSU7i1t{igd1K2yB?u8R91vOl_q!6Yr^p#yW1WpRF?bWhy{?J;TIgPp^L=N&hoL0)#lwGn>gCv6%Jg5v zPavzAAO!mYl9&9H{q1fna<53;vNnC*2FVc>|{50K2NCV-lNFhzLezWrCfqF=4oB#LSGCT zZ`$9xgFSU-MP%DE!Cu~_XP*5lJvkG>s5aaW)Pyl^7enqdhm;CA@q0x3dslcle}6=^ zKWV(9eW~#NC^0(v?`k4rVkxd8IG&cVbT7iC+F~UOU2>b-{5)c{_#UgaOR-{^T{XBa z?F2m@w)r=l*8VhCUPMo>Pi+V>I(3b7RF2iAgJK+YYPq_cG^6{Jg01w`1C$wNg1F@r z&AvrxU1-P26k4m524@l~S}rT^7E2%5H8_iJz zNc3SW5v!ixcorvF3|jB)1E~Vs1GsAo{T&wRUGLCqJUx=4|1|a@#l0?lP(J9h6mwVo zpQbh5ankpjmMGO=TnDV1{-Deah!X&@l*CCctK6(%a|W`4=VDF@=w0dO^^{beh7z9E zaJhwI`xprYgRB6Xf)!XKc_RgS%JmymkLl@1vMc&ePqC+`=;?)#{okMEjKzfP-k}?`_ZiV1xs&eYzW%c)q zsVDGe!)Ju+!Bm>^55^rQbQ_bn-8XiC>}{qLHxcqzM#KpIpb4* zb<_+%a@JdDbN-}HQXHYqnTZF&0XI&Jrw;ajx}@B0SN1#E;M_|m?klv)!w~2HJnRT5 z?j{v`1K8kPrUnW+%x{1lXXvyB$^jmNS+54V_rw}V`Dv~j56o?#qnKfX-fHlR+$Y`j z@qt}=kF*zODj(oe{F@#^)1zZ{HyDeC;mirwl)*HJRL&1b3T7_esB=>N zh*R7Uo4*dd+Ex;qx@oCuPOB|bznf4BTW_t6{rT^AVkHsy8+E3hal-{Xo(RAIxw1Nca8_h(6yrG?MJ9F(?Dk=^ztB z0f~DqqGS?-kEbUMKtER1D51>`I*)+3s&83T?%dBL@vx~~Yj?92bxRNVK~QC!nFp(r zVgsKt#BI_PILA<+^BFa9sLe96_Ink5+&Cqk=8R?8u|v65=ONwslekqvB~w<%lURB{ zl(6sm^s(hJyQb*S5;Q-<(|IXcU)7IuXZ6cA>ZtGh)upkLr(@MWiL`VTRrG6(WsYLR zP6-vSei`6m)z3F>5vv~9R2olT3S#YYnRx2ecxu_tOQLhMA*fZbho={dJEg3uOXT-e@BJ_NwAP1xB-43&_v6ITop1ieanXTCD{Wt7};70+r#hew&=s z{p!f$o*Xg12B;A8#%$TJSd;Xc2ON#5kDLaE%+hV(Q)RFy3+@*tl2xv$vRj_tK+p)&!{H#M{vdNN zxB76tbjn|qaD!)V6HR@(%N+K50$$^q|2S&`=Z1$gm|d$x*;vGp1W9TUXJtcf`C_xs zK^i0M3T~vm_KVGPwT!l=A{}EKZe_ZehYkF5PRZ>mhSyjnvFeSH>AKz`mikP`Os!SF z1$Lk{*;CImSCniQYg@bq7}Yl?9nT=+zJ9U5ToD z_^Fkbrf8&T3tc+Z%o8U8^38sCPm}TtH5N{{m^-hn042?U4f}$mJG&NH}3YM zpMjsaI!78SSrVgX!&}MoAz&?^Z992IEHiO!tmVD!NW-@sf;Q6fJY%HY5QP z#eZWxIR8ErPpEIuHJ_tvUJ<6#;|ApE8MzL*$ok6i?vd%oBS>|IF|)(jY>vYdv2#nW ziIprpeDf+hzrkYpLT;acDeRW$18LfzQv=bKOVylJU-C-LLfb-r*~=bQr$B$xUkmHP4xdoq&vky6(~;g#VG!7w^g>kOfKa7h#s zw$ACwAr?fFz7A7YStKwruXth(~IiuN3^h$Es8h2@Npd-F?=PL8bQ{8 zZe2bn#3Q15`BzYH!F3aeD9urxC2I2NeP;k|C5%{XBO4MRiR$!!j~z{U(!Gf191GS_ zgeP%~i$h{9hgNlIygrt>sN9gO1FjWOm_<4enHQa-iKqh!m6q?Y*}BAlc3xlvpgQ%c zko}SnkXp>Odl=}4_KzgWw&>F3(ET|l1(zNA<4EFdrX5CEIsJ=}_E~ld=_5hM9Ho)a zEpO`Hd4$fQk;hR9u@;Z&!R9aZVosxrG8Gs=~Z98#; zW_HbnV8k2}%aqpej|42NXToF;YRd^G9~9+#*x)?*faQCL zsk=zxSP-l11cUggECvJkBq4&8Dj`B$)?X0jSHV{S`FQz&emXlRFT#8GFt{);Lc5n2 zK|O|f5i|(qMhTT3Q)S*<>A$(RK6xWo%J}mi?7j|SoLAKh97eT19Z4WmRXNoR zGWDu21`gGTvtr<|KkMgi-HDO+BOOow3r`Bg!0sZ~qGI4ssv`z;cehz3Uu_WhX3zLuMF<#8aQc3L}k4;J^!W z_+x^XAn8V*KVsxadMYw~87KSUkL`W_AV(6qv>->4$75MO2{fNrJd(Ix;QQ>$h@2;d zs{lsh;QGXO;ow)%N8*mxIf`>R^Ir}BgrAM z3NPl5_^B>^Hny6Gt7nywT{JzqGCJ2&y53U9EK{c{lKJ~kh-HK8pyPsr%^p+RKYR;%%cycOdMh;s2-`7@-db2|etE zFN#HoHmY70neJ(k(i+%JiPa)4Q#Id~T+Pc^1o4=0f~L`0%sK1#!kB061zJuyiS@16zo%Z-lcyJhp2+(44$$Q$KgxUis#zW1PTIB; z_43ryF(26c_uOY~^4?>Yd^}=!&Xkp+CiCn8Fj;@KR--zbt9iiS=wcaJD0zvj1y5K@ z$wMAAHLJO2I<-H;gW1%Wn_FY^6{_Kh{PsTunAbKzAmj}5nXke8Teo){yDxlrX3-Gj+(3su;-yxCStWDX58Qu&(N1UKv?7o&3v~eE zw^=SV7``~5oge133ly0r)7|rUpNE-C>T<9$Y0y{E;`0A1j*%7?{G9@STaiWpe=KdB zlH0uXyQyt$d1<{IJ<+?5fy)?7j+{jbzEN1r98#BPT?#&HNsY{^HE)G~p;aE*SKAqE zC!N8T7w7-jr8o7zUULNn*&fsI82Kkm9w)3j=PBeGc7g8{e1=~rEjGrqyTxij+2Qn~ zGR6Y0i+-fbHvP-E-tPjj5Iy_SSkFy7APZ%h1cJS`x3Ws%rX` zQVb5*g7qni+`e-|X9{aKmS(h52c38Rn6o2^X07DC2ct$c4kShDu6od@@p+wVtvOtt zfE6ns@65>c?u#JQMtv{rI@T7)kgY*8^a;41 zVM#hsnZ3=~!Tk70KM_TaGV1MP{UqsitsjKxkx$0vka{HljZIEMXhkAGh zcgsW)(}Z|@AIR7|-v?=h+MIqgbB{6~MGc(^KL<-W^|w{@&pWkt^DjxDo+^ z5FiKYlgA_w^(u6PcAY0)Q5H!4-MMPjaB+Cs3jtEpnia#^ax^S*y1L1 za{R!9x|1bRKCq4DTF|aS0%EH=>MoL$@+JBwmY(NvG51qck{J2un*p2mOM>Kh(|4}~ z1hVS$G(4>D2OT!kkr7elBlsjJCNrwnXT3IE{(K9RJ;Rt(E-(!SzI{5EZzIVU@h`YX z*aGO*s#mBY|HV>1al072a)RXLp?u9=<@sCM{*YroU-laYP`F-GSE0XUm40~smIhVE zes1+&IY*zoD!flGn~(B(iS9gK%L*?arL_s%EK9O|fADMd?JY?qI8U*@akD5@r1K`F z?iHS?CpqZZZyobdZiQ5FtNG*EQT1(YW=5grJ-cmA(7t7KNR>Q>jwqdH=c? zK{O=i+eWn+xIZsf4~lndjS~r?e_VX>S}n;Ziidw*fZTdVF=Fmxyjw& z%B~?TR{JCaV4FyC4Q*p{E<1nX_#qRsv>!>-N%7CQaP$Qi4nKQR$PPMgeSdyFr9hgj=Z4xBE zO+&{v?!a$o&6+$4US4V)c`7D&xlHlj(pj!Go7d?18w7EFBdpm`9VmC8W+hX0fr3Xp zJIfOck_kRSL0q_PAj#ldgWf2R2z&4-D-n*c*K{rsju3x@^5AmWM!vu0H@D`o2jQ{gduY77j28^BA-{9y;W$wU0TI~wB0u%qQ}iE^EKY5AlfZMq4izqjHfwV=Tm*$e9Mr^2Y!Pd3>4CHiI(S409lWov zM_XZb{j-7VG7`h}a$Enb&RhTdOPKCQ3-=!%!PA$PkAYqoasI&icmtdNI|il@B%^!L zbI(ZfTGL}t^SDW8k7d2)*>J>hno~E_jOPz@;#QDC3D_UNnYpe4S6qe zJlj2*cbGzMp3-%4B-w1-A8J$#i4h#r2Gh+msJy!Y++|-f4IH;hg(Zt zd6sG<8HG#S&lz|4Vb4^uD{!SO)`ughKn{wpuZ-2qtzNx+%tIEan)zNsIa^!F0 zkDb2@RRyy54R;sng|qoHmLct=d^8ty(;|11#)V8kB1Muv*W~}kCLbwLy@IHJJ^8nu zbi&D3&)}7r!v=WM>&%j}D4g4SC167fEbwUv?Nd#+aCFp8&4_n@c6`Xj2`dcQ_{sl@ zjZ+xn^5zh3w$DertF2XQsW_<>-2)(I(=l5{lGBYRn@9 zEq&SnH_NDof!5rYlGbfyL?Jar8>B6Hdaw^~d zYUJ$anGlUv2ShXVS2>6|a9TcMmUn7CY^j>X5VO+6s}O^9IT6j3kCYWntKE635(p3- z4QjvHDpjv&deXh!3Q?0=N0Mh5OO6?TiB@$(LzYZzG|Wj&5&(sqIB3w>sL6)`Noq1- z(O^5a(A{t6gNa#Aql*)qjw9vvad_r z7D}+|%@PeIS0Kw^{wGX2=Ncl9HG~puffa@ltlK(Cur{bj63iU2S?SgE6;ST%L3zWj zA(VrPLCGn8rm+~5KQKT79oioV-BXz=thzB6-I3%;_VQdTACjf#vU^!UN9T6V?l*?) z{;RY|B#)x^<58BPk7@sw6n+17ITU?P3*G04!OkgqkZKmAXfuDzD2Af15n2k^e6{Axx`cxXR7`xzga-!OA*lS zm;CX$ERx(y=GZ|7G@Mo*N!l4H&rvvftiQmjE{P=n^NwH-XBnZ6$3VPv&RBTq6@*P^ zBfBMGBRie|Z-4XRVad|Mk@f6!WH+1W4G8cuZoYM2@m6T^aYmA#V`4O4+7r>L6#Y3s zvR>?GrdM(P?cxEpMHaB#ivjj#r+{rX(U*(z+W^itHoMYS{<+eccCm0f%n`5NsGa2? zt8;zJnhIw%ow?)KNa_ra$3d2en-6~w;&Hs*Ux>%CrfZ8)$nXx}K6ezjoFSxXInq6n zyi^Mv&n-C`XK^I4y|J=wo{iEPE^)9HoaRUaEt0rLpNhn-o9ap2hu+GY0I1|nEo8`= zaV1ppcVfMRb48u7!I*1okRR_94)PZ;Mu$NrZP{IHkQb@COu}$+_h~(Y{3CSk?Td4U z`8;n+TyCb&{gf%Y`!s1<-#f~Fkvyr=D(D22g~NMNPcmh9vCHnhfPBjH z!(tIy7_7Z0Q`VdJ=Dcs=`PuimV|j;7LXSw2C1)@R7oI(KY$Q37myDV^r4&DL@ z2P-hS*Ey%Hj!cpnq3NxSwORMTU*j&p#{rHC7TW{&gw@*L;x0(exjF%W`yLp^1s zBDUP#-W<#$4$WNv9@%1Zf8f2ZH}A`PosL!H5X8N}H7Zr`wi8Vv zn-Zj%vUf*z0?!VoXQ@?NyJA(Po^$tlSNQNLw}6tiy~-%}&%Hv*-5;qX%FUysYc(kt zLLDioLe)dD(pAoc3N4H>WrueL=!!Es0xEx`d!?zdS2Lok`< zU-X{W$5Qo?Dcj(`Yn6WbT`EFNbQ6#^vnkg&{=Vr*Y23YcEq=oy)un<$8>^Nf=DWyvjTeTZcufE z+iKl>pAY-0RmDeVwXT1_uQk_dLECT{!n%*4BpgzCx&l2B}s0T|J==rvhq{ZWz z^{GjOy55gY^*33Z{9S4LxdL{O8+n$tl3O2O8`irbeKlVwoc=#azRE!@!$2=OeiHXVFs( z>`#p_Bx+=^fsDUk3^@}gd&qe6-x_jAHWiJ82$uchGLZ46pw5&PtR$}C z={%hK2_Rv#QkFS!KIK{fv@EJQGkQk!On2PN!9DdS+Htpq^LG#&jD zA&}$Wo4Jxzlay-nzxf|dA5};XuFp*FTTku-?tjxfOkqrwG2$94tPvMT>6#+~4-X|T z2f;{-S#0Q1z;eqPLRcqPFxFBIdsgbb{1=VvIIN~V#lLiqNWX;jw4r7na=oye)fL^5 zhlcMLc__BOcj6?*al`V~_j=5Kqg&*m<*{=)P`}a+x@S(@vIY{U=#j4BDB$XivH7dY zn1QZbPTCsF4Bto+974y2SK7&Huv?`st2tb~i0jdYaprdb>9V5faU6FU)yo1|XW5t% zRn7X$_@BC*i8F&v$)!0Fg-fkwoAZT~lrq#X@y@Z-(BYSp`D`E|Lqq>@oPGCpLOmep zQ$F2zs$Hg5Gsqy?wL&>g=YQT!kCsqc-|}T;Wcm-Gidgcs#(I8YhnMaykWNsRg(Y|d zNGq`HJ%;iWc@;{OFNZ`)k$w=|Vq&(IO# zAw8KRwF`|CG@vf3!LaR<)IA`o#(8NX-DVCOwuPC_&W0x8?M4fA#635NCj9o|PC3V< z?M`4A;J;Hn3GQXIs!wrxgBy`ug|_#m871%NyNtXpn~6%*KT!}st#roG2cOh&9;7t_ z+dw5lj(11a`~Q|lWsZClgUd$0%8%r|{LtHO5R7b38+xkO5!rb0ctL%uEfzKm@yU1J zFGRjWAm0=AZ*_XiUk%N+D$#^xZm6xOfndA!&{f(Y!Nwujl&*l&#eo~Nj>$wf#?lu9 z)M)ssmgQ%flelo%1QPbd)29pq+8PxMudiMhNsQG(r~2ba;yk|SsJK=f@ziUvbhkRL zY>%hMLX1B{*v9yW|5qoVgh~*8EuVI0dlRd?hV%tB1D9vw+j$ox#~Ip8dWxl%AcIHR zWyw(9{MdY#OG^E3rQx^o@LL7n9;s1FU6#Bga^(DIYS^Hh+md^u3FB-cs&_4n5Cwhh zs?u?lG|KckzP@dk1R>uKeNms$^vUoM8}|*Fo`Y3^e@n@H)%b&%o!yJsQ2lQ*ex>fS z^ByFp)o5DPC7%!Q34VmCC46m2ebJCw7y6L~>x#kPKIQGJG>nD4>#JXHdaEJzrF;o$ z-oQ>O9$lG_8cC~tdfmw`80p_?y3*;1$k!7lQ`h9NR2mM>otys71%&Bg1L8289%Lqz z`0iL7=sFAUTYg#!E2D7+aBJ&y`?O(d%Y7)KSE@nNzoce z+6oDprFOsU6Md;}o>_KSkrfM+)Mcb|OQWT8;48h~YQ4u(7gk2LJ%w~$aMaL>dT0wV zaz@qg#;sx`Uz!j}-jBVmtufJ-@wSO|93wOQ;Ocl)EV5%wrAXDJt1tF-wI`y#h9T%b zU-TbaBf0M}w9+oYoN-93`Wd$N;P!aBe_gt%Tz66@AEgyn9*?x>l(V34FU`PitUxWz zz}7!!{|`fCDmf201Rz#*F3w|B&LP9Y&Vq}0>KlmyMH5o#+{$DtLsEtD7}=3di6rZA zXp>lqdqcg|Yz%}cC(G`H5~49Ai=<0)jEsf*(cjcXQn`5t?`yh5TQ=eB9)Ww2TevN2 zL0x(*A^nI%t> zV$gV<2%#6K6|%3H+Ll!LRj1bI8pW*ot<;y?MM0PrLDDu7P-;0)Ryy^xRxig{FIQB> zoV~BXuvW4H?5z~(DPEG(WMs5uO^Mj_W`oJg@ZtQ&=2G&9&Sz3m+h>(H&MN5+^2OXF zj+q@?$1TOD%hgdpuv~Dj>~-=U+P$)WQ&^TVtUU-b)s-xcBBSd1yrW#r8XbVH#RPwj zm47%{-aIj3wgE3}iDNs=oK9U$P(d(*%NVcOtCnx#1&!Wkf3P8wb-f<3LA9P%U3C2O zU7H`~#p~KSw=c zIs_AB3|T`yCvC4~jy6MrEE3CH&x*i&*rQg^FP7LQe^Y5>+nzrm1JuMdpmdAa{5MK1 zv(Umw@&S5q@8^%+pTkk^pQQXeJldwkX6MRhc$qYn)>^~St^2amSD$Vg4ok;kWIjvu z70QAPRl7sYo;gt}+(7s}?eodHbVFs7+#g=P3G&RruLxC)HvCsM8)7Jg$OLIMkQC0s z(P(K%RL^KJL4C%gxHI#7cEOktQip&pOp<-N7_T_ziFG<#=Z1C8U{%SUhq+>zERKg*wc6rk@D88zASWhFoWaSVcb)BNSD$X^OWIu0 zL5&~OkRFe5kgv*(f`6UT!m$48uJsRXoAj z#)zTOQZDfueLUMbi=`%@k0(eU$5M6Z*M#SLr9tc}!>c!x6(W@FpwmdBz~n+5~MF7)J$SY|Alrl57Gaf|lDH z{qDd$Wf$n-zR!Khht^B$oIS-)oiflyiQyue=F8Evl5&K506k$~IS)UF+3r7eKC>!e z9K+z}#6VXnVy{CoosfB6qK%uxw4NDF9}DZ8j3s;}|4tf^BsCSIMskn|a$7H&pPyt>_gk>gSZnU0! z4e&mX$kz;04;>I{0`KTo^szF{l*E)iQ?Cn|&3r}XWoPJG9ym9v<4})W2wE%9ALz?o ze4x#?iGHyeF|EcJDD(wd)hFvXWiyiqpUH3jXFu3K8u{oCEpvfyZI>>!ls8^V4^CRC ze{}V`&e{p6f>W`Vvi_f@zMiXSJ7k*YDtha-0#}jrj8g8J{hFLaaHtXhN0MWRMyX3* z!USDVqa}+Bz{F@p3`)xx$g>UfteJ45zed4)bRJ-q>oUi%MYfzFiO)5p+CEDZ-!oF= zCIcd{9-^t%S}jUSxrYA0h4tx!nS^qKBXPUMFKpt<_6=Hl%3Qbx)&^hdG+NS%h6IWF zQP*cl`yA=P(2kj)49Ey(X&h%aBUT7Z=8K;6aY*bIP0dGaVv|HI`QidC({>N)yMqB2xPb%8+8^;h@Q~Q-wck*0Zde3DCSNO=mjd95z?}#Lq`9A=%c#A!aI-;Wr`=Lcx!0) z=m7+`6T8>yZzMmRKaXO$5#G=354gCQc0@Sd7Xir*@Qz?Ayo}AV(iYD&F<@(9=MBjm zPp6Y`554BB(-RV0Jtq@K+-qEnH1QlsVpMUj&#X#ZGD`saBX=rk9&+oR{P4N7ueuHR zE0Q?K8{`ieq|akTmY0o+j9>QiVX4FLeC90uz$hRe)?ZYa}a`>vHMVwdq)G9WJwE2_$N(_cU`grLvSpy_g&a;<60B%L?87mBcughw%YWCy=v1FD)?#wE>BKu1hVYH?lXh)imDnh zkl4l9sQHK9SmNz=w!U`e**slR9o`yXBbaTiMbaBaYUpQpYX}z8Z6}9r%P%NWl~Ou( zyMyDvE>(58IG+(Xm}xKQGc(&*+k~!&2MERX&Irvf!oq>5oIf4wSywR?3lwL%io=EF zVrOsr?RkIYK1D>BQ4)BI8G_dRPQ>cnXWc4PjrB<4UJByrCS+RK%tcr`)kE2W<&JE` z;MU`bK_w?(iJU9@Ye|*a?L0k755*#yT&eRyxJ~1k((X!l>+y^7-O3BO?9&)IQsyh) z?T4}ILn1BbNTKyZlTNI_67=%j)>p4rvfINWiT`k(1(!Uo;JN`akOwJOIoZiC4Bmeg zN)s75<2?Dfx()n<swfLc4QrW4b%Y(}$_Nub zDB0+0+3z=q!0uuZVeT0(xuM>4Ee)KZP$7K}SF+a$>Q+xhF*2}fGttepeOd!MXOZlj zN1+0E#c#Ov%5|m$rhKblN`izSr>xQ%n|$a62-;=MK23L zl6#o-)TtV@Vp4pVlC5#R8B`2bgRezr&$3UiV+JoY4Hpfz$&r*E#v!p@HLRigqS|mX zFNq}HH5-&XgK0dRS~6*Bk_R$1?msCqKL}5m`_1kQ)zvK(o^^XDk*w}fU^h7b6qa00 zM4cfZxeNA62fO!T?MfHey*9O)ca6wtJqupRTtzLWS(B=#eKvZoFpS{T0v7$n?Asf) z&M}1VEBjV-`zR4xwIvJW4(sz-dt0b~%}!5T0vXGxkaaw;S`~Ny4it0*(l>r9fiKgv z`3cQ^9Tpmx7oGH3j|)Ox{>+#cnXQwc$2y<>=SUzL{eM&ezY4I6cvP~HbmM*21J+Dw zlfHzBNLH<Xeft|>2JH{0$5m?Q z+48Hz(r2+K9EwNQw|&j1f}U&z*vvSbFHoSKjTrh{+nf#3suK9VFKNTc{LUQK`o117 zc6y(#&(55#YvB)(t5k>l6B`+oB>aakusjHOGP9cCFCf$3z*4!%bSi`!So=Pq)o>5Z z>fo{o`jPw$quO~<(x9cCb*gvl!+lZPtnhoNuWx`v-T66miq)g@d+mC@4Fu}bZ)%A< zg&?&+{%Nnxqx+Mg5$Cp(cu#tGB^n5*ddK=LCCx+k$h3Q)xage9L&&MpH9mUW`6$&R zOBWy6y)xD^tg^@B$1!H-IAN`@ZCPa6v9x{sTx(i6s%hM))I?PLuu67zyT?)|SMuF| z&&iQ#r&1R$O4*M{;~blF{IK)K?RV~k39;0X!z0^vRl%X0q>5~NWQD#D_r6!^dx8c$ zAE56`yyrFkTkuBZzNI8G?T-kiM=JRT7@*xX5O2e5_%-P zud(w%Op$50%+^~v&`G)`RCGTV^JjMZ{9WFkDZBMd>AS6@?_i)VG$TW+ZKO7PLwSAU~@+G!B4S_p9ycuYW7Q;>a zlW>rl_?;BINv)qrVQ4kH797=;ydG^?h!g*%_J#PzJimhbJ4V9rK0eq^i>Rf))}<*N z6iiOp(+ApGH@gy?$>g|^4{Xem;qEO`?U}NB>B&o#=63`db33|?a-|~DzDlHt=&Z)Dv`qGyaU#p6E#~k~d;csYq`b>SR zNsLV(?_DSd=i}qNcZ`&T(c^q;z%(rHd_WzdDAh6Vl0N^)^g5OAKd|*q zNBO=Tt;A>yQ{GM`p?rel6MXW^>1CzwI=k}e-pg`X8f!cni)EBMn`lP;-h*9a{l3%x zeZ)E5?;H88RbFQc%VOt)`!j=5Ct6>(otm@0yzCf8m$UwTHdwG1TVFS*GH)(iUmp!d&6{TK#vnv|ihw{^xaES}%v3oRyR!GEYZ5)|LF*lM3_QB-qQbXV6`V zout&Z!!}T4L3ZCF+@6ReVhXA2WQ)S>PhqDm|003iS<9I!%#@u)5?((urssi!MvS~f zfs{W@xlU;236smPZcLbXj2@SfLNmFsJcch2xFt3hp7agGtgb5as>ciM;N*j#aHh63 z-NS)0SXXLxWCskvU~tdYT0dg@gu(MiS=_j~iLltIZ~1FwW$xH{f8acZ|Quu8y5ETJKq7NnGu_{tz}y#-|vRiT_;F4D$E3< zwE~kef5Ym)7X_XdW-{Bbig9`nGZ9imW2YNd4XO9*tLb%?qOqAX$|!&DcrPj zhpKtjDc=8e{ghMOMW70+g03OCydhHeA<-h4(&IBd{~@S_)I+fGQcF|I45jORl>TkS z=FOR-!006&Mxhf)e&Tu93G}X@*kZ}|63{aYd5CWZ%F+vj=SYHUvkiZ7El#p5mK)GB z&u~>=@@X!fclYV56AqH2Xk@&4N#jThtmAbA_XzD$Sp;)wk8p`SRZtK106v7Ac%Cc8 zUoH9ZJZkC3^R)6+dxh#jno@HqrQyZ%I7b|dFy8M!V*W8z>{5)W>MMFW<0a-QJ*nK; zA1zy(Y2e2RIFSak^B{9xC2pP8p0O@RF_&2m+io>I;r?OZOz+| zT9k5PEa2)hyW)d5z(O$16uL>sHB)Gwde$9)dt#5&maL7oJXuoP=gUyv&PA*<4+nkG zhY;jn9tHfv5r=bWrKgYnkf)dO)VI6c`|(T8rE?XQ^r!F)6{gzUQ&n(^S1^D+0s7k$iq3}%aBZqM%-a|f0)HpASOhB=H# zC0fYGT&?N|{Pz)w4QvrcYM*s)lp2wJX;$GW@{HJAa8yU%xRvrhu&{Ax?Mv=bzH5CI z*K`t&5yz`B$Qj2l#SjGL@k~qBiJ(ptHUO!=ia2+S%aYh z^a}{@9v;<}$R#(|XUU*Bx>*T681wnSDt0lATtDC%LI{PZd6I!?0~ zMLn^54n-9wrqyxHyGKISnccXw39J{TBhgnL9SM-Xs*lWWw-ZJilF*-dX8pNUf-JRy zySO6LZ!u9)TEnb!LEFMe%e8vY?*+N6wO0-YhL+KK*j$E&af zB1&w>s@*+}rGn&Cr=7iyy6d#F`4rAU8sXk+O}!^IUZ07}skg1SG?Y(!XHk&(^wzUl zhesO+l{|@?v9`~%?av@j0VVkZu|(?MiiVX&j6;UAgaxf(?6;>7*>W6=5+p39I+lu7 zM7gwcK#6|Eo->b>JXBr#STCwm+)7Yo~vy=b;JzLZoGlXsM95`z;K4``D<4 z?M1~PYCIXU(ESK1|27l5g4Iwku}TB;HF(qYy&i48BY>gJeX@9~H_Kwgx5w8X(AOQt z8pxzkHQ9lBGRYgQyM?&Dn@1zu3HQ1JDY8Fz!W@dRjCe(GVy`ac)A{WY9?9v41hfjJ zhse{i*}ap6w!dB!$uHEiH&{d(86=`my-gYB)fRl$K8lH^-%$??#v`@!-4pqqr~l}u zJZM zSWc@@BR`%uxqG6*!|@EEvtT@%L1N)}_SDPP^T#tJC&6riA$bAcJ*2z|A_8oAQGc>~ zOq47UUN`dxhf)?y4yyo>^XXyU2=zmb#Q@C9)#yPcNTF74>2o+xp2O7P#%}QbwtEXD zv67diiOi~GzD?Y;Y}|J8Ua`ysE)jIL;~rQp5nLZ>Sp@J9%wrluKrpSbk~W_GNzY1H zX|hcyQui(-^!oa2+sL#VG`?(R_ZSM_sx(SD#{^&{DA1AEi0 zU{d8~a9>Ap1fy+av^r2{_phH=pITp^`a(OOEo+9KM{t2%u&15B7$MWT9+jhsv9{7G zP>OX~>m4`Jm)^mXN`2wMpx{B@;LCvEOO3ubhb&=O$lAUZlv>)_nmfj#&a+Q~rgkH#~wHTy)rO&lgM{SpVzFBM%MTFP1`k zZN~{MT^gk$S~{PVZtPyOd36r$eDHA|J1Sc_!!yqbdSG~C4 z4!FmF2nk`|1fqZof~-9T2rewL<$q6AO;7hM-80U;RXy+D`fA zkCb0uefE8)`bEtuwVGKoU4cOO{L{1b(j9&Ezel?6_p3plS~`OnfIiZ*_0mzkq`~Ja zKi_vHv#@6u^xfN}&xd{*)yIW&sJl>7st;d%{r_D)zVa}7_m!8gJv92>H{Iy_|1`b- zba@x>YwewH{a%o_Rz4T>MJo?qeer$&U(=ueHN5}p^1gsyzdc>BH~R9qpg;QZ`ETpb z1$(KL|9@NF|LgjGL0>NDi$?;1#`C`V?fZT~e>~FT^QRZ=jaDAM`r`Y3L4UOJx}ZN= zdHCv!@B0P)(aP(B{%Galr!Vo^`D-l7+pvy^s2?@Tq}8C}~;tRj2%HRE>A-{vv<$q$7-<8-(UH&xV{QKQ1JlRJ1orxQ9 zMT1gb$wv8IiFxYskG^2Y?`U=TCC2%0Qs{h zmWcR|@cY~dzi$%Z_uJwlFlj``ie> zZxZ46+t5!%)czyykMR542)}O<;rH8OBIG~9?{g#kzDb1NZ-bW= zk^V>ceQt!`H;M53ZPDTJPm}3zJ*|#X_ec0t<)C`z1C?~ALHa`m>9-oBUu}?H*C4$j zN+17vgY=IK(wzqB4;iH2YLI@lL3&+-^opuD-TGT^kp7WDy3-*2A%pZ=4brbRNUv*< zUV#XCdiuNGApIkQbZ4-1_nV)9vbVjs8-66-Y4mvpBfJ#J*-10#Y)!#y_7u~FIm0o= zOO|g&;WW@|?HxT*a2W)IWWi;Y{iWfJ*d%(Pm;cLI-SP)VTK-i1@~i%g5dUTR<}c&@`d=)Dn3F zkwB}7*S4Aj(P|vOb543YH zC5m_VK2a3CXXMfhRKFa^CSlst4wzkPj63hF=J-daI>%&E*!Brl=Wq$vMq4QEJYqrP zPSF#HfD~tO4vbBb-$z-JGXX(dZJCm@H{zTqY_Y%Bo9zXW%~XIJTB<6_ zRs6c*nm3~V9pQLbHgWO+LDsmM0HzL2MytQg6i}VFP z<-MaR?*X6k?1z%9t~k2HM5`;f&H#c+A3}m~O7T_W67ERdYIRx7P38Mh;9YmpbQ@_= zV-{a5FAf4qDw8E|@j@Rln`m{c!-V27t78@B6xWk=*U%!c9c&GgRFf)ls$nQyk<9&iPxdzrumtQ&xuIx8P*)E3~Qe!91 zTph=)K3#S9VubKSm74*J$MswhfA=7-(F>{k&VkF{yN1d~s8W=_0@4dqzNx5J2UAg> zYn=VtAp}+FuQ3#bYUtYOh}Zf=6bmxu%8XBU_QC9HX02M1efLS+tR082b^@Z5w#}6P z3|c=Kq2JMl9jB$V6Q=z8@e_r_Z}W%L2Fo5Nq&B;xwC$$+VfdYJSR5smHYcsPWzh8& zizitXZOVU>;&8gIzY(DaO|wSO&#wLnR@bf6v@I^F%JlE$*0fC-rsqpDTydRCbJP^ zde2d-t6n21dYi?u)#_Mlq0rj_8OE>0|_-|%dmBJ%ruAf$z-Hru^5H1i$=h_s-mv^^mqKbo@@K}ZX#rlwjDv72V(f3ckSQEGDv z@-`JstpWiH#O98c>_@<5gY+3`B=CLK=yK^5%L%B%c4n&)r_cQtf*_JU5D@wck(r7b zA>=(iUKKs#)~Xq~RhG0L@(#NDQNYcjQ86f4jy4rIQPJr$u4b(tm4zs_satV}n8opf zI~#BjB(=Lqa-4$(RnaYb_LG;Ho|?kcVQ&5B)hZgQN??XOq6f;D61c zO%0pHKwp3ZW;|_k-hPYYZ;Socqqn(Y>-Kgvu5Bfvo2_Zzn(`+CnzhZhjNB6x{j%nS zE3D2s7H5|%2ip1#lH(7qB;``{X4YbQFlHK=ic->NTsI>@a^*xrMEtsWQ&CQ|sc2ku z`i$|>GqR(RR)eKAWZ&e}Kw1r?)tHfudz^A&SlThLjPKBT$HgEmW=3`l(qdWK4?Hav zX|YI)osk`jv^bXb4Nr?hS{%~iW@N`94aa$aoYY^-N@)*q5XYgJFrfQs9$qOJ=H2wThJy}BlM!CmF7Q28r(L>1z9 z`|i_}6|Ld!LF&MgqSIjg4eJ1W*U%&gX-o(C)!*U}NiU=1FpTHfxV(B9)4Q4{+&e1I z)+)7R1_jPU=Z1j~E^GuDNi2hT_a9||qu612Z8n~=gs#)oM^ssIAIKT4 zFfK2CdKV^>hL8dCxFGy|R8v&cv`OIJ4N(s5kWq4|7Qe#>tB_`VnqN{(StL`Yj9W2R zu-?isDkfbiMCcFsl#jMRuPsT=T!fTPA7I~u^KW#CCAzcPxHYWjNnA=7+iCzxGu8D# zXMY*~T&rtBQbs>Got{cQCQUy?#8o~Gzne0(q&SW&3L>etrtMxdZcWFAw58Brsj3P?rD!-1C@n)ZbO(_$VTo$-8o-HZO$IC#tAh@V!0CXrXYlM7prR& zuCs(7enQKlfbzJLX^-dKlD5Lu6lmqkKufI0mQy}Q59r$zTnC8-*&%#E_U`@kJqPa{ zAGDRGuOR;5TV)K&xQCoyULPJ(z$cf$@2e=2Ehs^enO9z9@xqrGc`F{7wJh<-EcYNl zdSsRcvVJm)#g9T}ZpNurW|K5Bd+#vu?3Ee4D>D1-fFiTGlrMnHuG&Z6`^xM}ADMBx zBFK=uW|I+IEnkeUHS%Km*`Dhs?jUq#!wK(slH_OKT}$XEevy?onQ3bhMCGHcd_2a= zYe3~Wb2G)bC3P#`rAhUJ*7!a8;9%}59q)8rSCtJUJ;3I8@>Z*|F(P1Z*m8+CPg)Ym z+?h@b?{HhIb~FFq&>t=&og;s^Dfz>$j>T|P7xQZbD&!h$ScRWLT4q-Bzl;{+TByMcG>6Yh2Ilakg(NC1;qLB*s_Bq}&(f?{8x) z>fH*F%uE$)n5xE(0SN}y&oE-AYLfk8HWdCnQoPOR0nkroE{do944)2zHY{(u#-cUh zvaP?&N*&Y;)hsZSu-rg-%iZVi*Dps?8gi z1mH1M4{F2AO4L)z7gIR_2XK6m`zy$WAiUQI2vH~>7q=DWddd9ep29ovo zK(Z#8>_KJnx!tO!q1fMkG%*k!y&fuh54wZWn-!2AhBB_W=dr~CL&$zPh`cS43+NmS zRj4JQ`<0?`CrE|uQZ}A)PDVFUmLNAmQTR3Qhy~XyC0k%%Q>3(&{G!YgyO8FJn}l5p zcMqb%C~7uFpx}_%OQP2@OnPrqnL+3ka(eN4^gc&oIQ(6wL$4{P_cC@R1m&-LnDoki z3C`bpl;(|xT2(V=(nXU5+MJ$jA8r1w0P8KgZ-S?|Fa; zN1lyz=$+WX_?x6huVt9@-lj5xwD&?zFJ6z{=SU2Pzw319HRbeP#;$~*^6VZay|V4W z`FoGj(EsVtD@S5D{N?D->&)pDV{1WB{)UE0Z|}C?{H>-m^nZHv{zPIp{EgS4*Pqkd zfvp2U`Ll;fFGh#n!S5M=BlPGs3X|URRA!L&Fp<+cSrr$YzqbJ<9C^0Xp;wF3o1sVV zx-jW|{#|hXUZOPge|q%FkQfes-F4_)#p%6&PDh?OVbUuv56<6Ll!pFKkKSG+hQr@b z9eTYuy)|cb_!}Q4y+5}G=Wjcuq5spP7ZWBuyAHhvI6e7{4u8)BOgQpvq(kq-7RKKs zJ$fy}r1v(J8Kk`za(eN4^gc&oIQ(6wL$4{P_ws2Sd3FzzUfJg0{JlqM=>PQSl_N16 z{&IBab>{SnPwDVCG)#JXzYWgcYDz=@r$_HkB!+pwZ`mpH5=+Ha3 ziSai=k6xoN={-+n25AoyIlYr7b@+Q5V8W4SOC5T(IK3Hq^sWn&-sc;G^Y;>^q5spP zSBAuJ`0K7i?L|ANuO{-G?#%8b5Qjtw-oEjFA4oHArP zh)x8WqP&QlT*r@AhnwA<$+j5Lbmb-VyLNiCK22Bhr8=yQ$P(arW=NI!yc~cHN8aV@ zb@-<=@P8cg#$J}C<7gJ!JCEotM7jW=&@V^kaOmgg(C^IY7at2kzg7VHIbqT-U#G)A zrGfuo^b3*h$3HTM#lH^y&YXVnU%~hfKtCr;`sHhN_@^}RAB=t>(*5{H=CJtJq2HO) zFa9$a{{iUdgh{`AjSl~m2L6N5FGRW@|HvE`|2p(LbNa=91miyd{hToAm#^00pVGj8 zF#3f^_v0U#!{T3uerHa<`1fG^2cVx5CjIhNI{Z@__zy(KAa=@%aj z#(x0%IbqT-U#Y`CrGfuo^b3*h$3HTM#lH^y&YXU6Wib8&(9a2ze)$R={wWRo2cutz zbU*%)IV}El=y&Gyi;o23KLGulFzJ_<>F`f!;6E7sLZtigkIZ55uS35xr(b+H82(*5{H z=CJtJq2HO)FRlp2e*pS9VbU)z(cz!cz<)6Mg-G|~ADP4AUx$8YPQUoKVEhN5pA#ni z@?|>wQyTaWM!yj0e*7bISp4hI@672JyMyr`fPPMx^vjp(@K0&rKN$T&r2FxY%wh4b zL%%bpUn~dXKLGulFzJ_nqr*R?f&XCi3z6=}KQf2KzYhJ*oPP1aVEhN5pA#ni@+CU_ zQyTaWM!yj0e*7bISp4hI@672J9|*>O0Qxy$(l7s7hkr@~|H0@NBHfRFWDbje9r~R) z{o?(>_zyroCrtX~i*@*?H1Hpcej(ER_($fj_}8J|nbR-c7mWV^^mD?bU%p6(e@X-Y z!RQwv-H(4{4vT*s`kgub;=RH64?sUBO#0=;I{Z@__zy(KAa=@;(_ z#(x0%IbqT-|4N5{N(2AF=ocd0kAGwii+>&ZojLvDUxV= z(*5{H=CJtJq2HO)FWw!D{{ZxJ!lYmRr4Ij;2L6N5FGRYU|6DqdsGO-CqS-o&KyGX= zuxp?>**gtxb={A1ij`E)&cuWxr>nA@W}LqqNPCTOGJ*C$CuJ4vvpp=~+yRan{K`+? zxH>*DoA&5e%CT^PEspOoPmZH9E1a3;W>!}>9C|C6m?xF)iIrmd;vA>r3aeveJQ9;! z+0j%aPV8cnx{at*%3H|G#OVa=j$I+8?Lx4)Wna)QnqYAfrrw9({H8Md?%BspANEMy zSFw`c!(+v10T5iiD1q%8|AkINB%_)LpnD`vQpwMuaIydPpUP_)>4h_K_n{EvMaMj` zS$&F&%j-M1j1kJ~P4Y@L@_Od;|ERo;C=xrnL4gJ3L9o0P8|77>c^Uv;|6mAHG}CnOk1`yL;Z*$ic}56V_RnUcxX5@(mveQ|z?mQ}G{zD&U>nk(P`9aj9s z-^dwJ;x8UzZi^@WVh7ZiIPbHUW%G-_IOcN|$M-YRBg6M0IWvm<<}}md#Io?F{gBjTm1Y4ZeCBe7AY= z#RSGT^J9Jay@vG2Aw_H5<9rY3kGz`OTy^MSh#0 zJp!MdW%H9?Jp+8rBF6U{IYSD*L(FaRwBHV1e0y0oKYXL+>&tHn(j${!Zw)@H2Hz}b zkC0#Y!1y+Nq>t}sq(_GDLvpGV`OT*{rGAf~b_Bk+SvEiUwKKriHDY{qH2CUi@ZILc z7ZVuY%n$YD_Zre8lix^kvK0A^qBli;3!yzienVL{KlvT{Kp)>3q(_Et9r_3_z76!I z;A`N;SH`mW;jn%$&a(N*ubu(EW)b82jXV$q-ywQa@OALw+sm@~;T!cH@@t1wTK^B}k;$*O2A@@f zZx*yi$gg`~d>h`?$M-YRBg6M0c}R-<=F^)Zzayv}f$wdW%};*q4DfZ07+)O?zIqyb zw|Vi!1jaY>9ew$|hV;nfH4nKQ{=Z0+9TvQlx6di-=Vqs_|70bGJNaE15xm8 zpf?3y124WZmdy{J)d1hXi1D@6;A^MBHwM}xWcb#R2cqEH zKyM1Z23~w+ESn!bs{y`&5#wvC!PicMZw$0Y$ggE!eDhz?m){bkM<&0g$wN})HK>e5-^}8}QaqulM=j!Tk~0?z ztA8SHQ;pQB)Z=mm>yi&Hie>AAiQ{avKDestduMlZY86F|Mr6gKEc~)uf~={>DQsL3 zp6DbHE(dKfk$tcTp;I9i83Ef6h0FWTvwnl6QMM?Cl{HmhCVhe^L{TwYfb~jwGk(u6G39r}Pta1iM7+1cJF$j9z9^VTx-X&? z;Mp(6AwDdNpN;i$5%a5@LKSO+`0~Z^t)w~jv$gXwrQ&-Zd0_CpHrpdoT8t@w5%F34 z$guI=pSIyA7T&Qn(M_gBO9YV;v5I*dFZ%D1>?J1OP0xmfw?(z#{Su~@ z_CJJ&ciF|&CEqxXcg*at%A;jOAR`L2ZF1>O$f;r*&nb;&o8g^uEcvcR4D#y1TL8lg`5t*9EW8g?n|!~AAp_oUsf4-4TO_-(JU1t!@^su+T{Bs3@`9r79QSS)rL2Y;~n!@Sn|E9+VJ*+;e~vcJ{lI@K8Q(QJ@(-p z7+&CQ5FXxIh>2c3c#px50q;GN!jkXcYQx)u<6Ss0EWEvFA-`JIvtEGVg?!IV2!uDt zdE-ZeOZUHa-uN&K7|t8#DmZCi)WLUg6*e>kuI>LCz6CI- zkYAHf@U5)|eDyiL3APaA_sY5RFTc?+n2_JL@gd-Q^vwCkw-E*s_&SAxuk`f!$Jd_Y zd-0JF#$p$rj{U1$4f?kL zh7a;<5)!^s=U@Nob9@uVh9JL1C(b{<(J;)A-?lL!;B)_d{_$;u;RC)-q2PP=`1!}z zp5uFQbO`c$vl{Bx1;Y&a9Um0}zK5$peurWBfG;-`e5uudFN@C7?PKk0}yBfxGaYSO;Jf5Y5L*_@8dssaFC zp$@G(+c*9%40XjOdDUGZ;jNnLgZC*?(esIS0SqJXHVF-H3mk6N$afp5?D@od7KRdd zhu;~JduH4o>&c-xqhD zXZd~u!wbAuhKBc+TYT^y`1w5Jt~)AvsRs^7a#wQ;J8JAgePw$?6=?T8dsL#pL3b zamm)`tyX7xf>gTvQYrd(i|OMnS>5LDE|K z$sxsCWBRwYpd`mQ+(q(xZ`bvg;4#_i$i{UxC^5!_OdvNn={8rri!CU;bZw&5At89r zM5IoPm!dZzZj&YYPvAiLWsR+l0ZCHy57y{a)@TqJ*BS*Ncb^1n^cFm(;G<;v+3M`m zCCk+~6~ry2>riHP3UVR255CAw03TLIl*QGoHb6JV+i=M=rtEGMLJw<)n?{wI8m6}{ z<1w9hJRN?q(O`(=8mV+|tQ1`#rB&E2rZ`B>$KxgY$^_Y-g{n>4nRmqEY-hIES0q@| z_S+iXjW|gN5#=6+R2GMb?$HrNGXhHasE0oh3duiSRKal@?gKg@@3}25D#K|tH>1!_ z2^kb{$igmX!L32auy=K=nPO5|UFo<6XwAf%|1^z_kzBFq_;0zXA%NzfTCx)|?D#NR z8+j^kG)0}FTYdnJ-{)g-(ZxWx)F&C?q9&$#3X;;w#+#)!<%^Ptlw-Nv6{r{t21tr7 zuP9`E(p5l|N4`{(!95(2jO0SAr}=CUwnlq+ErU9s8xEDtuMv1l5hpk!&jeX;i;ZQ zfFzODr8tg?;7TFjsEaEd0gwHFATy z&LPqj$EsYRV=8!)3ZK4$Sn`JQ^4ya|LAD$aE+S@GK@(M1&n&-#GAYoTtKDY#6)N9Z z&um2%BXv2Vj7e=aBS@K4dNNKbJr^Stl$d6HOl+6#rl4n%^I8d7(nxaNEIB*YDI(!~ zMZ&?oI%(gV3Vs1_Q;U535EYfN1fZhay;yW9#G{g&kHuSEz2hNgL=kh|;`uVp>R4A% ziqr(8CQ#}TZ))BitFw2KM#8Ce2>=}SMN?#HOpRnpJxB)-AF>X}ywNdgV ztMgHWG0;U5<;B%dvi<#qs<;ZK1k2+qX2{XKp@kX!Gjc=H!MzZhvh&4$Ae<5K5+>8XY`nbwDqK4vvBj z?n95L(!tS>@$WaDrci4qU1xFZKvX@t)lA&dwe9lLRr15AnhZxtYDtg$k~|YEQaqU>&wXQfWx>wq zH%>}rKS8gb)v*#bbiFkFP!v>LV@gg^-3bs*YTTr66fcj(#;afa8GcH8nwt|RtIPYz z792s1RUtmz6mv?HZ6Mj0E-(jIT*Yq`>voD)CDPKlIdL3-S&&vkwlz@*Rc12g=r&$K z`D4v;Lv+HTFfuEPd+~Bk{Y_PRP<{?RG5#XnUU9#3Rrx>VHA^k2XwK>rAAbjrPAb@H zDj0&=LchagAvx!mi8q*@#{BlKdZ;F1pLH{Gi(Wpp4#GA zEIB)&pNQ88uQh&$9R~M9B*(9kv!>*{NOGnp4Tlj!e_L6FE4-$dheX*LlD>@qXG_hf z&6Z$Ga(0LL7-dc&)8 zi@crsR!{E6pwO-@HHQ=O(vw64#h8ln(W^z_inn^^j8*yOVn_u|GcTPC_WL9|{2kJl zTWjJ-WvySpE5t_pt1r6a_L}BDM>t2VQM(jOd5!XIp&x7!)xaE z@jzLqmgz5biWG8@hgh6qo+P>Q*=OLH_=g^^Hs#lXj>vyx#znc$GSkoOyp1x?VVR}0 zZMIIsO=ISnsnO%1%;K=om2rKMXLY`L8h)jJ!%g?C7@lF;21RyX&GpXlqy2R3#I};F z@g2BlFh*|H6J7~=&16pvs6Ka}0QD>v{0+%*j(GzaXdKB@vK2u!qw%Q)_vt0k^>UNY zi%vrV3QnhjZ68*=Ee{3@s7-d3rr(&s4BE*d}@hY315pD4CFu`uo>bf z9B9v1$8!6CSg9+LaJ!!zD+qYxX>oifM7rKzo+x?_I!lU#`WpT^v#b`!N@ou^SJ}~0 z+PaB%NzQ&~X$M$`V|BD!zPJWNY40ILTOIsb%Xs;^TTuYJdJ^ph3Q;NFPG|v7wf3Nr zjqpPyS6;T{dOUqmJW7-|0X67WJR@gzqmN@-CE;gNU9XZ5Ka#WQw8uI`nH)bsl#;WX zBsqJZB$H+TwH7l$xN=acy+(3$JIUh329hNc zM+N=<$&kKB99-Wz6!g1=^!*`!9T4Bw2a$C*u$chn))r`^fJl2~^cu8P=G1ZXB-caZ7TRv6iex^ zv0099Eu~el7FT18dzPCDE0IJa8rCCW&f#DzMnO`{d>P_d=<-U12Z2kaZ zrD5(`gl>kJp*aXls^H1g1pk9K&=dXz|JrND$HHGB<>1{2bH#<9+#?m>*C;vnJP2n1Pa2W?MP{LJo6byTt|d#SFIB-iKD2;~DeGj5sS+%#g6zwfH* zk23maIaX)nW;r%x^uwLHO}{h(y4WB6I8`K_hY>=vuz4|{hhT8=_@NP3V>dN2TTmoHW=%Oa`E{av1eC17yQP%moo5O_5b8h4Ur2fKP2)9*qS?qnH_MLac z0Xvn(X1NafK;q&e>I<{12+*zSiMx1uVyf(B%b9mU{jj``c-Hy?bki^S0!&}D{p@g5 zH}$ih%0J&ADqe+`#>^gLm4nW6fTqIH+t#w8DwBlgkM1iSzony5!TYDKmrRyi4nQ>M zMYC`AJ?f4s&x$bfu6ppUcSz2--yVTWU3xNxvSV&)B~9r~8FE|$lU>cpXVzr5lGj8lY0T*_cUBx0q1H2=O!2 z6w#tuPPRH@F@J<4_|r!*W9nXyR!r^P8oh?sx6>-0Wb-8s_(t}_(Rqj6HI}$|*PHPh`#*KwW1$h#>Xm>|z=~OD^iX z*lYnaJn{^5ZZsy&7Gq-clO1i{$iM%M5B1-J^NOMVj7+mhUf<(fm8l3%?#oF0qdIHE z&-_bw|G%{MqL+{oA@N+HxhHl?u!{X;D zNS*U@=6ZjAGWuIxH@$_vGb7jP>bUn3JoK}=+S@P217_7*jex7idBAj={SZ8~#<*ew z!(@%Bb%ClagtV*gp-TlTG!kmM_Kcm^P(f6WAkj3ghrdmN8zw z6kQP|wxlkpp9G0uLK-U^YfXi3P}_HArL%c_W)dZM4-!nDGXCV|fX(WEMg_rEx3_XzbzA^RtpVc}Z^X>ojA1K^_>eU)E&Ow2>4Xw;J|;WFxQ0d+P00;V>&*Xz^u5mfPe|YE%>Q%#2R-M>gNLIdlm&Ub|k51ww0|Mc{?o!*&dn7_5o*ppS1c zY(3#yPBuSed~>LghuohuQSi-d=WjolzcJ+^;cql+DEy7+MB#5VH7WkaX_~9$X66oX zDyEROKZK6kj9Gl*qb>AW_#DgdyXeEeV2{kr_yCVInNW&Ogg#hHory2wV}skNSKz$J z-+>Q7{Z68Om7qQ!oC@k0Uh18^)UPJ$%*xRUfS9^T#~?yI&wT);qdwg<;PBQZJ+bi= z`Da!V6ZvO{ARXqPZD}Orb+CRQd0pW}{N6|)#>^OF{}huW^U@_L0jgD~XvAUr8~-qnh{+7R_DL4AssdP6VunqKOs9}GcWMJU}sUR&!4c{NH9 z^7_1ikk`vlJ>Eu){MldHDz!TlxR#C+Tof_cV%24}d@Ftz&SE-yWd3YN;)40Jx1tN9 zd^yyWMdHuyp@KC2Y!obve4`g)$p}CkFCaGZLVO>;3y81d5oo9->-)1dWDM@lb^=kY zKRd5AZ|91>-ceV`|9KKSjVG>ynzHdk5>z=f`QOttP;9{^j@^k^Jw2C#04CG#C>3YcMVhKsrJELO^_;)eD2z+6%Ec;R;3m ze+}1@e-VgkT(%6fwwT1i}k=RN8pHq#{ccB%%dkCt^r zeZ-@Mgw`HzyHcr%^83Nt3BlWyObJ;{e2w1)Z=bM7Ruk{wk++AhVHc<-%JA1y6aQ#@ zP5rS}{NvC<jc1TJ&f_NVL1I^|O68lUVVt-a zY_h>3Q&lUbgfU{o#x5E*CeyI7wR|~AUOj9~C)1?0#BHH?l6P96Y_>+(WO90_-F z2&pZ>qGE6N#0mXsNe!R$tE`sLulJ#Gq3PEV6e%ii{C%jrkyvFRS*&7}$|~<}{4Vq> zn>{l9>VZeDUw6aq(I-QB7Xb)?OFNP$9&fG$1 z7Yek`v5I0kbCnluGlIw)yuQ8tbFhxREdougy)`HM)W_BuqCOf?HL(7VYKZlJFvY6+ z(3#Kt1*Hn!h62yhLxQ&=Rzr-pTk*T7iFEeJYN8_^d3(4Oc8_Wzhj$VGyglrpf;8&kE*>SiNxl$L}8fXOCR}@hJ2kMvLh`bzPCj ze-9O;)qfZ_`9?3qlDh$Mj)2(63-NvY?$Lidh9rL*GV16*h-&3O?@~hNKS#;>XBxJ5 zJBRw9YOs5%p=A_RgR6aQG!Sg1I4zHBM$%?dJ$4sZd0DU`F%@HD^PTveHrPXZ77i#eodt{nB8IQaVeHKQB1pNx{Y}9>d5u82GN|bucf|aOPwI9f4 z%*gxeLT+KY7{O$kCAl%ovsRn0g@KYsLns3F9|Hh;g@E0|3;Rd>E?|F)N1@Hwli}I( z*&ROQ2<_nIU1U`4Ur|jx@$t)WkQMrzlc||zvZtNT z5JT*H*UU%eqV_S)sF*HqXu|syE*({SU2;aW*MS~I81lDJgf3{fqP^0N1o&5h){38m zyu^%Ztz1-EGi($jIo!o6lg&?7_ES`SAA4kq@&g`u-%<&CMW3AD=2+cAY5%vUg<_xE z6mznKwPIy|6t&4D`572iZ6jQOwl2%ySs)t?T1#69WF?S_(9nKfWW5QFUzdI}Ng2l6 zJC<`_j*PUQM>C|O_7^W707a!gLC}FJxo@JkKbIVxSM^E&r+qM-?3GMszq+snHWwnC zHkndNZLzDmZLpHuc@no>?QE=j8nt zKR-g7{=OHR{*pE0<|N7a6dgmM(>{Z&j$~(d+VtlcG+&1^Cg~EkiPJ7WvVoXl62{6@ zV5ZOPE0R5eQucwXHrdImAPtmRQ8arQ>H<4=_RBeqQ5mj|_2yHZd_=p1q}52mABUYm zam7u;xd^-^usu6*&;5YYAPzh29Kr{*mo~#xR5QQCW=5yH@gv$T+k-x)BNvjBmElfB zRe0>?3AqI31PpG*PQ%e1*%`sqwTmwzlCF+p4xd8lv1WE0XcB&t-$HJtrc&C;iD@_| zM`!$`Hp?aZsj9qAP|4_vq}8hqQDqmBopQC800NswEsh>haz1`?k3^yr)76}Myy7Rh zTO*Vgy@pNJNzvajV)j!FC+v}2j~tflzg;A`CS4?@{W1O%WS_5YuR--^FvJ>2e-<6{ z=+8|sD8~8|Oh2noM}f-UrXA?Lh|1rE zDh(u`T7RPa1j7FfEZaYq&l5q)Ur+e!QTc-+D!)aL@_#1#!{mP!e#<|{zwDyG^7)g> zuTS`2kEr~?LCSCS7s_u=<=+%h`Be=A;~yz|@b5%3HLBXGcZ|iflGNldRv@Ez_s zV!uJ-miq-S_AbL>6L_%+8njEGv+_Cgz(LTqG%PkL%4P*x<_*LzrhWuypE`tM-wRgk z#Bq88pe^J*Quh^X%}XOgpCGYay(8i?C2$uv!Udy-)bT#^CP@;t~UyYDR6GSS9}@DiO*XgEgt{q=)q&|9X*W7 z`M2uBza|9yFv;T|O`94&-c*oF9j}+#+<+g|!Z%SoKW*tF-v{Wh8M*%0fcbkRz@=jT z6dj-tZJBE$-{J5Ex?>!Y#t{WmQFrwLL)Vm;bonhBN!zi5UvnUu%42&ZoJ}=WAs$x2 zanRqbgENl3<4=JSIli>pq%l7!D0b=mTt}+{to&g8dkd&~p#JN~Uf4HkVDecww#N1i z@;Hfuop3MdBvg5@Bi$Bu`X@y$CiHZqdl}U{llXp{Qmv+ZI*&_+FNHTE|0CUf+~llP z%43(1NZE03-6P$n_HpM0SnZfgkDK-;^Ecz8puoui*Rd z>6|_!e}=p`I@f(SYtW&c>+V%ssTAG%c&2XP`sfLM7dNE(7!A=G)<;l(@tPpzyMIOb zjj8TD+`{(j`CP?`& zSD<{Z{}GjcO_1_Wl6=^ByM8F;i@6cn+t%8d+fHM*lTu5j@2RSKBFdEiCMuMAUQ^L+ z*EqJK@5LD;Q&EXDbS;iP#Y(Qs_;l=quUX641IMTjoWzd^4zmhYUw(ZCKQ^W2_)J?Ue?OpfPQYs_QA|?*Tkl|*IdnSsYiXczMJ6zAZ535WIDtyLg=z18Je!7*Gk}gdG|iNRu6_HsrUF?r z-*E%k2R2@Ep-e;lp=k^Tf%U_+*_UcMet`GbR$&)O5~ zPi~5+{Hhv(@xMy)Ve;RJ5ywB5&)gv8U!j!$ctqvj5~TcR$V{^Suz5t~?}`pApD90j z%KrvU|DVg}i6G@q-huKPzd`7`}F3y;VUKlO*OeF@H9@yKS#oOZCJdaz)v)!Z3=AHs4U1cnVz0|}I8$k08&ZY=s_TUJg zQ?3${=#*duEnfi9DhAv^@jHtKo zf`j@g;3!H?WP?{vPK0z?!UGezI)45OCSVRMqybc=yiikM3!MU2V7dX2WGlQF{H4HX z57#+Y<$hn8=Qt=2L1`Fj&6=?0)e3-`F+Awi=WF?_>IoM$Ztp5EU&fLdpPAOd_OX!Qm~hGv?Cm@bMH6 z&@rsgO1YD|P*eWfR9nti9Ip>ra3%_NPewlZ!&9*;hl2bWrG0o}L`**1(5aq?^I7mioePTvAGiphaLnU15LwJXf(QE0OvC?D&Dathp4P=Afc zdi-TZx;2Xqr-~twbfM#N95!)Ovr3HP(6l9z=?Wie#Gh?A2<_w6s z%gQD=B-d+ZTH&%fyCqR<3}<6Asb6*m#c5l@lA>&*$Xi6*N1CK`&1ZpRpnUL^@f{Nt z5lWKBvJiKkk6==lu;cfbCOw7`j-37xO_z9*u@*@}rgi8fPhoOuVbbB zQ_XW#>G_9E`Oh(>g0wT10&bL>W+ZXm-YJ?8)dOsJ6IO+3t872uBz}U$aopmlsz~zs z(-!-xWJEcMx29chwtYf!w7~c}eqigesPPh3<9R#WElD>UoK)*3S@MkjBu)!rN6;Gm zb_zIweZi$waV$U{X4t16Y7Ka*FWqpHL*8c+Yp=;5#y$J>aR}FPXuSaXjiBEr}``oA&^;l^E0ZD#K@6UF{(9 zKP_p;Y+W(^R?*mVe&1e}j5xRPmb9I=mKG;9tA3#HyX5!IbYnQFufsDO?vmqRn!+IY`!QdDyrd`Dyf2cijm)Fs~T$=Zm^> z-Z3F19$Up5Smz9z9LRK(K@q!2Y zb)nBSpm%fNeZrxTZ-0-g z9zs3d$*P5H+a$*hPh&@k=MVh?I8* zye^@<3u*)m+4vG90R<+3Xcc%iy5lwuh@gm>(4`~?S6mDH!sH-1yJR?fC0LvYFO4}9 z1e>Xept`ZpS4@LUZH0Xk(w4tzU`)$g(SJy3H;f-6wONA|b=0aHjZ9DcBnf4-r2THI zFQwg_hXuBtP>WbI#co2Fn$@NzvDl9mYbJF~kx!9GnWc3=pXJkdrRXeV;U9uc5&?Oe z!q_uCbF6WXW6#3BbKP|3jJPPd@99|VH8ACOKpJPo6&DXzN$fbF;54p|e{Uc$qiupx z3TbJVEC@hujc=)6>q2yC_rV=(_W|`6V*5cK093Xgu&s1*41MIf>E}XPVZ$DkxF7Lk zrO@7(wLXqMo`)w!iSBpFlE+}$98*8b2}gcp(jId+n} z3t*b+k_kv+PhKCJ*^9oIn4#G*Ncxs(Pf%pPEf9m`>?L8F*|p!Ef-6_Dh{lhI3&kT1 zjYH6u11IH$@Jgs1qlIGugjPxr_mP)SAWGoP5+AYx@HLL)im%79uWdNf5tCZw%r?VS zz%<*Wqp8Q`cg~PVZzQ_dUyVn6;z}I79gZN;?Zmz`y--4gH48MvkKiUj)sSIOuu1V6 z=my|kO6Cu9V~Ux|OGh~gv}cDpQ8|faLyw5(X8d{MQWzqZcq#v32@61oAdp|9awA%WCZVD0{v@3qo1Ur-^`c(r9Sk(hPfsF zL!tjMYBdD;zs#};{`s?tfBu7)e@=f+WhnfIMn6eKznL%nOMU2n4OJxmL!tjM+I%Sd zvuuKY{;cAk|KR1H)1PxB6#he_pQNJS%$NS9KJ>pv#BAa}6#5^-tbzZQzU|Lc@Na5e zq76>NGiz$IS#}#PAJ`?&;m<3TcKAI10aq;WfZQ0?z!cf$$^QZp*y@?(w1$ZIH|?>b z@I`S*V+^zuX%}zhX;tm8mC)7kX4+?)d@bnpfi-7~6=L;Tg1j?m*L{Obs&_bl8ZQ&fW;0z@R{u20W zYZ!ErOthB2I}RE0R|)q%1pVE?vT^161?} z`qIDKhyH1J^q}826#A7X!m8iREIY5?@A$K~e#`j}O8t%p4pzTE0txDHgV4ge+oFD7 z!=Qz#Y$31vJ4FrNf;439B2+zvze*c<{a%h&mHs~Rn_>N$IDba{<4FuT0G0KRXZW+X zeoylsl=^-31e5Py?hxwt8TPGV|2#=We~K^t$v*Ti<@9Z!AI$$@{({Hz*)IGRkIMtM zBV`o?$^tA3$K@-;Pg5f4$8ovD#SD$})hyZ#D-=SGh$XLfuI%f|I(1AhjdUGi@J%<;gL8b_|o zm-5hA4mZ0D?>D> z%~zr+-YuG9OEf2>ZCcM8+ZRZ~>jKd--{G&{tmUaM;*~eW9O_CA9#A;c;IE_LuRApuSH~mkc-nVJ!|O)U zU}Rm%Uw5wIuSIxO@mDKE{^nvR6HuSE`ZI%N=I3&tCpHeaF6#`45eLyo!E3 z75!u%`k%l>A^t=I3&tCpHeaD`_{QK>7kXvZz)3%ewd~;$h?02#<9*$ShucxA) z?4i$RUk38Cf${SD+?Bzk9W4OjFuxwmDqxdtE z&out5$R~kZy)Wel)gZ|Jim32s#Xd-^E~NO6{`&MS3iQ@j zP<<+9*?4^|;m@qTqTt=4-z^ohd5S&_fL}oUu}Yuz|H|~~-@8aE>RByTpZ@b%0ld1q zmZ0xadv(9MRbHLn{YcEelgtHcO$9U+j1??btDP&bVsMIimgMTO*)escJcAMe9CPNL z39h`sY%xY&iH~S`bhZ37|C}bsJTqN<%8WURSsWAVTXAiZsbCoDCbh(U538a~)GpP} zSoSszu-JRWt6G0D=zLfUR6Ob{fdAw-9#_WQ53z4-8_030f)51#O6a)}|J^&D;gvQ| zu0wU?>UgG%y9&p1vmC9U?=KlhDYJTI1K_oY7{a3QDFdypf%Tytq>%l5b zd-Werw1Dn~SpQVTQ7k+6FJt-h%B2*vl@eq44@&*pmHMZ8Lj9NWT6A8D`rj_*V}ALC z*T1OYu}Gu(N3&w}&tE6OZqe)QcvaW`Pd@g@pTAq+zkBUb(D~lhEE~5+?fCOXg|Dmm z56XD9Z zTW@>l_EqrquykM0_VALT+vs!Dy6v6spT@pcwTC{+H$mHjm4AcwAlgtvv41lcBZV;9-!zt;x4(S;ymBeI7}Wmk{0F7|HS)B-r+;8=Y>H?_b(7Hk z?hx(o9%wNdT8+TRsBxCVx%cb*<8i2kh>wTe!4 z`4_?U?`CY(hQI>Z<7X7T2&#Wk>}!?&^-{hGs(%~cXrPPp>R)Z;o1pslGW*7(e|}1| z7XGKd5`V0&OX}C^TswM zFD=SHjMGX~#G~FKJd5%#LIv2Oe5L#lJ_0iNPq<0=OFK%E_CkypEbxsnSI4q3nW^A0 zVl~jBJooo+diFtF6=*7|MfU4Us#RCqQ}~IAg)PkY#KQapu`nOY7Ur=ZDA>CEaQ}6A zETW>=C`;N=TLW5{ugw?c@v~T&uVi;B@n!j;%(y=UX> zqI(h^JmU=#*cw3ks5>KrTKo+?}S)SnPpVco~X(2fkp4&2Yg7t3+N#Ny11OH#V0O>Es8 z=Sl2k2^e)=ytcxy-2Dl@6!$PGD)}o_G#w4JLeEH$v~l6lTh%plGpm`D zg~l~=J?7qEE}y5Gp|VgrdF!3M%otffWCiblK8B2BKbd-Y2J){Kp=fMw@DAkf{)q~r zfjpT2st9GiAnzeGf2uCko*c~iPea+Pm8XmGK5qJovrxgfIEPD-ZC!*E<+DsWm2v@7 zf3|<^!(Yx;RXne>KUc4#?mHk5x!Yz#|9Tfn3+Ufoqn-a}*|>kZfj?us&zEgSkvm%| zfB!8J!@t1&fdr=La zl7ONdrh;>DVxS--E0uy^kF4k)9vY7alAALRJ54=(eNh+GZ9#X~V?_S^;65)OgE$}m z#!o?5@a(TdKNpyf5B~`ts2PQeba^7_2sp!rVDbYd;g$Nj=b_u8ugd~-aV~j{*fXCG zE=fPCWF45uYHafBytr?|}AIyFMnfY}~$%;?I2kn?Eb_-`Rji^WW=)ttETQ z`~loIsG0*$fzJ4O-q)0n{Mw<#BA}fTp zfZgvn%ph-A%FGuvj1>79Xa!vehpo*#cH+$-<$NU5lJ=LWpc|eGj+^o?p#q$DNLD9i z4(`Bxs`tfP?E6k;rJXV5pFv)o^tttNi`@O01z76lxl7_IY`|zjO zW>`#p)6pEuR-lJPAA%1brzQxXv=8@5tY49(T|1d-hth5LQluAjp==-dBw8E)8KV}l zv-Z<635#g11G3V+7&s3 zYSiR7gd&~kndBek4}ji?vkVyfFdN4igSBU88svA^dPHH$Z%d89)p5!^UiM@vTS(hP z1&LD(_p44ZJfQj=S*0xOm8w~of5yu9yGl>3CYT+FE_0G(`It!f;e_JRA*X?#kD6{vTWoC_)9nWl|! zZ$v78XD$T zxA4yKY67sj2daltS;~3GK_2GrK}WHukuV%v&QqYb{4fOklZMnL$#RS^*)=DVI8c7~>hfp(|#O^f)92zyGiwc5*!pD;D-pFeSd9MfkPXLeFL?#f5 zBAOE+5~SRM`U4j~%7)V9YcOp@jq)qFs`v|^e`opGD#s4@TvV)x_v0AE$3}FcX+a@= zcdz1!y#HnK?I_F>Ig9d`mFS9G?XMaH+Y%(m-K)hI&L~0d7T{Ek2y%xHT0;gHf%kyX zwq1$HoCgPzTSD#XBNif$2fK4)T%zhmD(5SyT|D?YF{q|ZC}p%jXO|P#Yky0c#qN!M1*JD zBTBR}jHWE&Z~`@+an#VDmaMJ8@c6g2It6kv8R@U5nc;9GDixBUrQRXDnfXp`Pw}ih)xY*?pX1ag!MxvrmDtr10A}zW)&o~VoS3Ejf zlyvzks0#8V`8J9_){yhK5;IV?ku>-G2johl#ivn}2O1$l2wXyB%7-7uz9&>JY-5v; z;>4#X@gA1QoI5m^N_hn{lr)yYz31CY&`oqj4mz*LnbwqixvFIQHkAA#N_N-9ud}nT zv63r$O}xryF~9CHb&9={9NQ(wCU!<_t(yjYsvIhN=uwfT?K&!6aFP8ifrU!{K7yxbk|oJ9RRA~~Tsi6yOJQ`2U1gG&2k0t%BN?NK#THPze=a29~` zR8xPJ=&h#aG{8e2U zK~S2)C5qM&-hYCpu_2z%zTtRA8sj9I<@vH^fsz;90!JP(HmP$MYRq&Csi_N51CrX={wSiWrZLtyJjA=OTe;9YVJl=yU(b@$CD^ z$R0lV6_?Kq#&}kL8L@o&8shn*h~xRN%uqg^BgeA>!$1B0;y!`r5@S5?eG##It~bQ9 z^-C_FrrGKKp{pXQVNn6Q4zlC;xpT`CK9J z{8(Zrp9dnx^A{F2^!53<&$)bh8{^sjX~gom-w;m?foJhDL;3WI9M5-bxlYeMKk^xu zPg`R=Uwsm>d@>F3?D>@A`PWiI`CJt_o-f#%p`Lv15P0es<9X@hh~;B8#IyVpE}zG~ zF_cfO$nng<@`--?x>n%%ZHXbCi3=l^Ppl!Hw?5|bxyu;O!37cHvAu01pNj;Zbzd9G zr+?&l_Pu3<=Sjreqw)I<#&}lGk61o^4e|VeSagKv!^MX3=^Qzp6>l2J=RSew5@S5? zeH5{Lt~bQ9bv~ER(~At{(=>8C^RVuw-`=|kJg15c@f3a-v3wdE;`xlC(?Nem8sjGfaFeaPk0+ZfOG_am0i{f2mI2t1364CT`+ zay;L?Zd6~7e8A!M??o)1OhY_--sgD!^`)VFu8JJb7iBodN)I=XYuOrcz!8 zKNElH%)q|Su!}c-_-wvEhUR<3iW^^k$ZIH8bCjK-W%9lAAQjJkTfIn}OeRCwDj{TY zjCOD#T%0p80m0}Y1VuQzg>Y!g5f@7~mf@W2SDQJR#XH1uMlxI6aK)wBNlw@lT}6u= zDRq&L)~Z{}1K#0+%Z{?$&Ll?C7{DTqV(Ekgmu_|3kBlwQ?~>qp4|ti?0}6DWe`|D)|a;G-(m_wj@z5ER^?pix1h z1`P_Bs8J$v2~E%9MuP$(pkhN*RFs6GB483^T~|@DixqpX7my1HEr=pbilEX|W(^Po zr77nBJnzgYyV+3u{{DXMCFh)-^3L05=ACyK>y|-yK)XizR$TQK+{Zwu?cT;<6y7<= z%`Zl*DG~5w9OWY`)C9a7>A`gUxWOf;grY5gA7_mkPb1()8~?<85kF) zS0gq0RRZy**|*h4VBcq$zXsd(1cBM!5&s3)Zq=}@oIrzu+=xA}oz3Xl8@ah>O7jX0 z?9k#A#S6Ohc@L5mkUbR@_V&^h&M_76ojtG)#{8XdAwpwNa^rVc! z7K^^D_w-jBeWLek{lOIs5&K4tFgdvtFCGa0$O>hv5fpIjTc3@6>p(D45L|Ef{s?fT z`bxLOP34TSlLSD=Ct0Dcx|N}+Dx1w9>=G@VXqq9KFo$iZk-t&*t>c26M$*+(w~>~nkyPF9)ces0R?37R9f@BwB-`Jn zEr~Cc%P>|V?PnlZ5&63IeyJ0EYVUY!7H|AAlG)c%(VmEe8|+PAN;n}R57drGNJNZ> z`lF}b@t*O19Xs9+=-?oBtzic@%f7M%896}-KSH?+@fe(VE&L?2<$Z-nedAvmLI>y= zqM{3J@S%K_ z1F*?Z6%HP*^Z+)|0qlF^a5uJrMHA#J$0!ISlP{hI^stO;m5f3&-B!< z`WE$%Lj4GH-$%il26&IU5Ac#lNVhlPpBTDbtLauCuuAM#9dyGgodfG}kD{+vFLRJs z#w4JT$tZ#Nk0F`qIsoKpzQ=`on5V7&|3h1S&{pJXd{y7P;HZ}P@giSC%4^4q@LWrR zj-rE!V4N@xW?9HGA0rCsCxR}wb*|Zmh>KQmHln?}Dw}E~62Bee&VvzbY?MpDU6-Cpj2r`-jCe?JZmiHGx zYRF$_WJa%e8@Wx|gR9id&pSw_{x0bjfE_jTqoQBq9D-*3^+I-dzJ zhlsRV`AjBYWfR}M`Al-bK**2t?Q;%bhQ`|d`-3a}t#=`DI_*qB@DF5A|2v7tWqG$o z@Gsor;SV=Ww(=H^I4h91wWygDI);H-;R}(5dWTw#;fA`V`V;4N3*M0G(QeQ)yw77A z#Wdr%tSL6old_B7YbL%dtrxu-&=JDB)sL^~ps+BT zFtM5}%82)Txo$)7()h`z>7(4p(4XBR1&EZy^9DxDXX98{%@Wb?U_AT<@07i{UuV@n zm%0=&ghk}Z@V)uO1nQ)dUE$w>BNcU))&TS)?D)vwxtZdHF%pojO7M3HYlD2`-ECk| z)o}Q#UQr94ak-!~ve?%c0|`Fe3A8^Gi}nflK>Vg0$Go37f;JYTrw2Bax$E&yH;9LG zc?0ZRf?VF<56<<68sN@!4T#f8^G&BLMa!U8MQ z6rrNU%Oh<-3==0Fruq;{!d(K!QdQ#1v=Zg$7uM_C6~vCe;va||4%EQ0t=OP%iQu^_ z+=<{^5&@R@hvxvx@tv1M7`JyIQdO8cBKS)}(QQ@;Y0RODF)^Bwxf-9Sy!4qt+--n4 zBIiPZXJ}<+NNWj2az(@f6b0KXgX5}N_`N^#iU{GYqRcG2F?9r501WDJIIYIp{_m9i zHQh^baHI`bm7%Sv+S^szFl2HyE-tgeO_!+q|AJk@H7~9`rKAll0Oy30k0KiUdGm{p zulmxlxB9+_x%$3{DL_Au>j?Te(R1%Hh+y8uxIHa5B1pRo5I8np6M2%xaMqKqAb9j) zWV34nsUIV&V%{sG-#v3&2vEruYlp812<%Z^$m9-nPkWW~RSwR2;VKYy3ayvKoBB$R z1uJ24an>1N&QO-V$KF>;{fG02C-WTcbu>FoFVkiGV&>pp)-a>ksa z?4GlXr|cW@lznm_)F*X^FOBSuvt?kT?m1tH%I-XsiW^!o5L}5N1v0a4O;q>7fuPuD z^%E^wLWlM$Ikx>f3I~FiDB}VVt-3a<$$QBF7`{h-7oZ0Gc*|DFygRv_EnhsZpDk)l z&rblUj6Kp%KB9x&Dit8`ocMY$j{FFtJp)$WInTHm?EC)hH2dIq+U%?Xz^2y20+AcD znxboeuz{M6FOj>@wcuUi%+*s*kgwZbh7jjK z@U?U{Qi&effglI~&Kd8GFJFP2V@`beYgwpZj&-yI8>FEbKLVrmaSkgaO+;Rym`vn~ zcq$;k9Bl)o_?{F8PEFgFUEQg-_!TRcJ ze39^Sp5Dm7F~tbrFwa$s)qRhnaUEHnWllt9#6J=F8cuS&6t^f&a@>tm9KYc-$4H#y z*x@TFLm%kse*B5@7Glm5A}#F}DDF$stU+E%vepdM0A4x^px2NtV6k(PXR8~k<1F?V=x&{-Yrs<)J8d-U>eGhJ? zp{dw}>)cPWLIHpeF_S})g)kks>gimdf|7_>VX-Q>BZSW-8`k5F7-Z@ACk3#vNJc^M z7Z|*%V{G`WJWSVor4M4{fU8)^ww zo+$UQ2pDCfAov_xRtOUbK}r&k5xfX5-7*Jz^vap+xn%B0(88ly9RFyyD)fL!zmYyc zYE_ojpLtzUi+WgjUDApM;9tP^0AVg6e82fhxAG;gUh!&y&sRo&I&vu`p=37r1u*-{ zcxEutRn$~IYueu%^7|?HN8@)k=0Q^acbFhCelg!Cvix6e`E*kraX>YZHDcDJef#@B zmglmEv;$U1w^}c?D)uJfm{dJq=@c2q-XveCIISgn6MbdJqBPL$D{IUL4bJgwrc|1t z-yYLW5sn1!O~Nv$)b9l-fDT{TCGsG{IfdYL5b#(Zzm`f)EI|ZbX0t2vQg#f=Rm~>| zr8NKYoiF>@9t#n;p=|LQk{@Ojl+T|=7m|k+$3F^&3$70J2Om=c(j$9ek_j{BoFHlh zZi~D^D)_pCeZUTIbsMg11X~O>sv->QAMO8++al|Zp%(LYCr3{`6nhR`oNeXx1sCrU z?&&+w3Jo62+%#2#{JuL@`NLz{5AqFJK?k(Z*BYL_YWyq#t>Aq$fpBIM7W)B3*7{lx z$OLC?DC!GAC38W?pb}TI!pY}Y6*Zz(@~O(LJ268JRDmkofP+rvq?r%pB-z zf{CuL$rOFLTJSx&_)i6~{?;utCG#y*z!0Hq-;>H}`y(72RvftmtLllF(e2WI4wq(r z#4hqGaqU{Ghfx%+JkjV8+0%)~adV0EKybZBY8`(M>V*nBO+7(OLdT>FYeKtoxR3n6 z9)fGCPIdeT%JBt|;IyJ{_&2@i8n9D_mAA6!Cd%)6GJC$Sl!q>?k_zawZ+vAqaaj}j z2LI4s1oKs`@%SSpv(u^;y6-t#bJj*E(e`J^=df0K&R@h`-U ziN}SbZ)Q)T(8%mV$)twaOKCa`*JFAg{UDwzvSkA6M}HbDLQaQwyF-mP zFi|yTn-G>zem4G}hRiaT>`YE5y2Z*{Jry+)0>19++4r9mH8jtusDnXY*~=&b`QhL_ z@+0^Rf0ns~ze3W>@mH_GTnaQAAmxbepoy4qSG@@QPsIg)3DfI|(*q?TS!4$RBRKV) zGSRrwg?RqUJP)+O7`1k=!zuL{y#@U`IF((Z8dlaIXX+7vt*)BjnPriF@Y>*(;QCQe z0oqx%aImd~yY^CX9};E4y+mB-t=A}Ud7#N6v4V~t^bqcg1aD%x49^GX%dk3hPWP2O zOFtkC+y{GR%umQF2n|a2!*?nOwJXTGBdxP<(tIXQYS_JWm(H66LxQOzuUmJLVR2fI zTzNHx8CJL}?iF2MoPy%T_rNWwSJjbrc3PVZ-V2JGsV>f_7u~`hCR}n`2;^<@O)6*J zrf#L%i_gaooTviu5L(At08<3FUSgH(NgVOcbbt=nbJs(5R0Z%>HO1C5ux%J)XEVk{ zWOrwbt=&e$x#!CqtlR8EpNWBjDDCubWw)C!$m;Z zQ>Hn8un$od2;C0sUX7i-n-T(fH>KtKCd~`vRTVen#gDQkJ0BQNtnv+-I921Jp1}hz z6XZ};x5u~|005# zGl#gjEMEt%K=S+yH34YymHt9)ehaiY;Fy^u60%qg3u0b15PpbWADY?PeDBZiTsJY_ zS)M+2SAM_2`A)F$S*p39`Z>fEuq`XjnHGwm|3x3ykXzR(3DjRdfq~@xu&r1qw^0fYV>{PhwF| z*(-?G(tU;(CW#QcEMj-B*^LI_CEA-42+sn#}lXJ?s zv>;1`SPO%~hlfN>lCCW9F8z!4w7%JDl<3~USRMIgkH*Wz)G#4z$#n%Y+2tnh#TtIpDF>HGm(QcZ(Bkr%eegG&%<$SdPU+dL^n-G zQ1b~Bw1_c?>ZoSDmM2m@(p=gYiz*nZkfmf_y`litEs}~_$zIgUBn*iX>UA7&b~$LV z5wuu=7Q*?)#y8jQ)Ccr&O0YW0U4v@4R*}E4*y&XW2d;ldt@<+#tA4v$_3w*U z-{gbhG5p|e?aN}-G)gUDy_CY4!KIfpwGvY`*fCHGvw_)amGfGivrK)(H~MFAknuMF zq0L}gn=JK`Q%-{#^s6#d`L`t@`Dy68In`UYQ(61OvjX)0@fi5oh$&c_m26lMoQ z;nPV5Tgwh@U#+*EAnCZO+6Tg6;l5gNNiDdfji)Ni8cMIA((BOpFTRxJNFlf+SS&mf zAY+O2Xn?9lW0(N@&Km(ID3{IYVYt)q+79$^Co8~nQoa?&)EOQD!Ol?cK}blmpEdFMFu3>-2Hfw1Gpr@m$jH@Yxgg-4`u{anxcxXz!d zKG&rm>7z@Zhaw>Bn7;b;biDEkXpCs5uV1&!Wc4J}wi>G|ontVQh*CRcU%@R~p>9J- ztiW^w1F25;LD~O`;kp`%{9D(|RxO4{9CyFGKZfkyCo@%w>HYE<`gsxZWB@Uk^nIy6 z%phMtz_HfK<&;!4=QY_hpF(4eDG9!-j{sTKlj_-fJwBBUpLznY10`ifBAeo#&Y6v+ zk4kkjStQo6tv~#RsW)}RE?m8gLAMMU6zq~bzf1iL`ppo>-1}aJh%BD|ca6G$4a>YB z!^*ojy{KS*f|PmQ}j$y8P;WHT_XC@(ilk{?>GGBH?-p&YT*fo2q)rU7s-1 z*4!(4p(kgQ<21S37`O3g{Dz{RN+5luO5#}V zV0F(}dSWcl2P=D_ISd?Da+_K+4F$WSpct)6wGi(ZDypfL=s}%+I#R5>2-2{hhDq?>!sJ~%RTh^LH7uHsm}6N)0`w5?>^;SG6`E&=caw-~PgboWF$-f}F?j-5_VYJPLB|#iJNfcvH}89+RW3 zskrWgc4?FIVkt)}>2lN5D|msWDfgX~@*iYt@s*5)YT0S|+LY5y0hDU>qL?ZK0(D$X zeR3y_F}$cWu~PZF<{_v{-8uqohI7~6q4})pa}Y}-v=AK_CR%~73^73pK*yLB=Rm6$ z+J?;BTeZ_SAmtWot1k3PRN#i(<<^+~bTW;xhhf41BIbuqT71 zn8|;tpWXXNZ_^)Y*^{J_TK0td8L%gQ!1#^2Vz|Rtt3LxlTNxMPOiKf_^TK zLeO{c=yJQre;@Go>sJ~6ehBO={QdHkn!h)K&+FvxZvc!TM^A!riUDH^sAj;(bHQlm zf^n*VQH#F=K!@=6iM9CquAe;o{TW}%c5!Ayd=LbizrXs_gO27d93SJmLB|{NDCl?| zj|b)N@jEqt&xG6Q@OK4X41ZsP>@D&5dnUsd2wKNIP+AIMnYH=*p(3;y&ixBZc9hN| z0gplGP;_AU`_E4fgTI&HoIxG@-FO)I8wcJGjlW-lH+~5GJwb|T{(e|LyZjx}A8PUU zsblNn?_2IBe-8ts1`m5a20=#{#<~R5HjK3%-wi=m$fFQ+As*xK_dK|wqQBn(I}3l8 z!|qdm*MnDDCx34NFb0gf2u6_sV=ky>z_`i<<5Cxlt^!6a{vHB!)WP46t@ZHtcYM+O zO?(Iw8T}oBU+JLZLKlvu_-@cKUmgV=U*hqg{Qbsu&EG%K@wD~#2D}*lz8~3K;_>%S zqW8()>4o$!cE{oGckV));oK9=D%zQV#~^efIxzg*3||jfe{V_-_y@7f4{3g)Z*{1qwC`D;GOa210{eQXex#sq#8*65Ja};16Ps*4$r?F zG70pU^#NS1GuW`lCv*Hu&Z;o|N>b@CB`Qhx|87s?WR{>V*2(Poz{Dz{hJ)vKBzK^k7!|I-~82@s< zg);VhU?B_UqM%`|>3DZ1kV)zcC=k^QzthQ_0k$=hg2!17@$juiqBUneaG&mx{^f={ z*txiJgHU$nn?@B(!>ElaxD?-w2zHf65yAF&bTyJYSK^ane_O#oh<-YPii-Ya24JIp zx*iBI`e|sC3xa?o6>x!C`W}FqVt~qYftrTz2B;6@QGj|4k4z}b08iJ$DBadSe=zg?roEZL}w2#SrLXN4BxVzzTZIn@Ov9gnt(%7Xfd zU9gd*mZU9OMRkxl3=-51$!tzPGF#>8FH}`nqv@8}T)pYIl|yKDN5bA=ApyUD5A+I% z@2mxP=Sk^Csv&_7=YAqPla=bBcV*e3TV^A08swu~?Sc3iX1X0eYCYvHniMyNpE=a= zGu7L-K?fnRsMy#8`j4I9s~v*=$&zAP|FqH1uKuCFhXCbT`e)9_TKZ=qV1obH5b!xn zsQJ&%u73pr-%@c2UFfm^t~0S?_WL6tem;V}W+T0E@v(u#_5Fk0EjddeXOQUDC*-Wv z>*KQ3wp%?3A@c{j{fM6zJ;|^Mx$|Y6-)3c1u6`|*U6#LBSYQqvntEV2^9Vd;0$5KU&#o3>wu`Y z5&0NG(Xm#==43{lB#lP+_%_v-y^bDfCH}<|{K(UN^+@%zEVJ8Ndz?I!N_$p#G`6%q zABz`>CQ==A<*GL{$i$}=T*8m)m)iHm zIkw*GaB=#u4np-OH@lyxchUi@_Xbi2xp;`8zm5rk{V&;4 zYbMKu^i*!!W)Nk)sTx8_Iv4+|@`A-+ES_VJ4eDel7KiOEID|;HWHO6T2Of&Wv=3Wk za^xZOQ8@rpCx{0lbH-HFLS8oTatgX3^E=n9WLdm$FV1LwV8B-*_ZrI%_LbOjMxRcp zaPF#0wau6XMIf55S^!r}yY4`}QC?&T8}JMTqR|3_0I+di#IYb>HrDde0(qMefHdW1 zpcTvXoL(mD_>-)t>7`junwh~dtproZj_pJQt$wDSMrDI^;+tD9nmdHZG%4%I-ciau z70SJ(_`uj*M2Y5^s!$*X30xQuW-(OWRRE2{nB!-DGtO~_rZlaQ26 zO~{ojXR}|54W4~jvWJkq(&s@BA*Q$fQ!)L7yXbx%K|d&rqPm|ubU)^zYv-EHH|git zEJJqjvj>D@Xnv^%zZBhcX#9d`rE37cqhG);pQ@Wt`QPwMwfJ8Sza$)hUy=pBI{4*E zY{yK47vvm#HHraogtd47>w{Eg|K+kN;!|j?fb6d6~B%nEOx%D!AHcF?{0jVzcM^PQ+PKIMj-IYyowsnMY*Is&!@TQD?!C~JFs;4X# z41^|>gLMKt#-oOd3nM&?^YQW$;nsYrD607wUFVvmUQlxhD%F)4Qk#4|q_)zd3&uYv zq|)Uh@8RVA-42&^rfcWnG98y`bDG@-JeT&S@g=W%xEFZ+NDfa#-|s1bS?X6934Oiy z4>T8^P7v>CSlRBt;A$8f=hkQdVcaQd()F6Z-R=3}bD)d7Tl0EtP` zxDZ}>AT-bl^+iz0ZU6(hl)cHdMK|HiMB41ukv3MzUx`JRcAmxEK%tXA)EjX&2=Qty zp=7@Cm*6KmNX=i;J)>SAxW`{oi8!0xo;aKOy178!U&a5Et6*&4;0LN~!sii!{EM{^ zWW7v2MW~k~a^O90E*KCW=NF>|uVFK#fL_2=nd$6L}qlRPSqz zm>ib>ulDNwg;TK z!H2}L)}aX0G7JO}cGzL>AJJ^nu)AiP-zSr8+;U4<&UOOwW6S{wY@9h?>28V!0!A^{ zEv#AMpcblgOjq6ZlWaSbGW;pL(vbn*@gGO91EH78FrGcJ5NG2_U(K=;);W*iM=9LI#ZWFp!s%?>SNFEO0Io#X_++nf(qn@35>`SUD< zFd`%s;gyd7PLVDhNck5WLyc|3l-f+s(qQ_n#W%vzf7*GUtDjxu)E0taA_Qog5;Z8O zV>S^(B>rvOK0ZbtMek{4*8w<%y6ZoHgTR?pN8J@)Ut85h%NN`WwexCX-iZ&*j6bZZ zqHrV+tl-p#ovB9wYKm@YXAVn#UZPX&+L!I(4aT=GpY{J2?RSc9f8qh!@AYrmUpFWU z{_F#^|3|-n0solj_LtC)18L*UPbU4F_FG1`KlA|YpZjmxpG7CwrEl5++W+pVe}SKC zquWP*o;vYU^l#d)xiJcUtQXeVe$#)`{!7vAZx??uK7Bvy>*B{b&upJE8Mj5kibShI zr{zPp!K?Hbk5V})lv57B22sy@ws9QCSfK(?r#dEURH&$4eofD8*O!MDZ+xNFtc#!a z*qa@$KSW&$m)_$L@l`w`{N(Y73T%%EKgRe&C1}oe0=)hZ+qUD5r4xx{rb9Htz(_Z7 zs5Xpg9W|h*0Z_&nVnclZZomqsymdBz;Bkpi%2I3-@-W0mgbDzTPW+CbyYBFMv=LJ9 zWvOoQJzrJP5f6UfS+>jxjMyP@+BsPUI^W(3bb8#dX$FW_FbnpU8;5d86cA&FWJSmH zmL*f1UhrsdB4<$4F~iZ*Jz0-2ZX4}ZbO3O7;UrLf2bxefe@g-*VT$&Etv+l_^MGp= zE`?Pf+eA6QIATKc7S8<##*}{44_CsE(x<7%;uo}I&s!p+q4y_>Uv(R28b_m-!`1(E z&0YPFaRC46v)&&5ckYYB65hBieZe_Vyl&wG7m*RSh3I#Znk#f*&-Tn4c0GP2YSU#6 zevG`qC2f30Ie@XRF_#NBF;uzvjrkeIq5-QC@%F?HC`9y|dln&Xa`)Se%(48rsHqj! zR}=z`sqMHv-T>r)SH|->O&`MMhjZKFTliUcif45IXWsxCP^-m+_d@7DZ(G#&`pRvK zh0bk@zoH!A@Z7d&_kIg=PtbRiI~M^OUHd|il9#~^B~SNNn_=vjRAMZONm7a2JODy+ zFT$-=?k{rlBG(qw>!cKh^$aSYP4zhzDCwDWgx`$6I7i`A*ImCK+YETp=UgXck`CZE z9r}o9k#TcLD$Q>=10}uV=$c4Y)wggdxL%F>2n!K^cqUnD))nYAaXSwf_BD447Rud% zKVf|~`ffp<$3?Y2@W(sd)$6ZrJTuN%9MT=0uahn4Yi?E#fh)0z(2g8c>&nja5w?hN zair05#0x-WNtcWQ%BB+4SS!UHh1gB_)W~+@MgPMHoy<$V()ZDju8K4p5M`(xLQ>gX z>XY*z_{><7HmDm&$we2Oxy0R>&i$TGoe7Ssk2O1bQ@l073DjNhlG>}WK0MJtR!Y~>b zdGX*9fX6V;8xBQyO&l=6cwR)7B=}L-ZY< zIC~*uYwKfMRD99*c&??s3UoFpKT4|H%xMg|L65y;^&>m-)iKzt|*&#ja9!+Lm#zZ~FEo7`M44)MEi0%9~LiGMjx zl92doz?+)*3D;=i-wi)olt$!MJZZf_m^KTTee7NmDzY-?+0^Ji-4}6OJ1fn|nD<6vJN6rO; z-P0prVdrAia%wmJIs*`@4VR)Vt=*z;PA#vc-5y12s5XI(>SP|Mu~-}wN#^ldtqdKP zqg%|)E@AI+`tXe(&R=4+XeQu7KP~XbXi?7RB1lu{eV3zZwc_=x(1Xl11FKqe3fT$U zEl$*X1O8CroF=B85B_DY0R2VnDF8OZtseUY_^9Ld&$7;5iM{!lI1($je`bI6Ppif)o%e5(nL%ZBBHaBeNBdM zjG^JMk5K$QcRlqV*{YQtt#Mlv1{US>ti}ggsFbeA7|B!76T!OrL>}@4j3poA2105X z+3?J;LhaR9Em_P|z?CId&Turd)#cgHM7)3e%GC24g0k4RTB|X5$C4XM;)ZFb<-zmdRz2LXFB#}mASbT#>M0>Z;iU^!WZPF1(` z!Z#)%Lk2R;w$xtikAlI3clBZNbS7WJ4lfgmAj1f<=`fp0-U3M-j!GyRA~OQI&RKW|E{R8+xW}U`Mz&XTdH`_v%)0!A zL=|Zdm&Sh{jP4{jpzhCbX#km!q}pzYz_nNRu^IS3$sA;G9aDF=zS3l4!?Hb4tmVt3 zAASa(#IA@;aLE)bQmsy-W6b-NWuq3&;lvtvsC}e4(F;^&MV=ON>&B4UxBc@c#=9VCNy?Eo{!_z*DV6+Z0g|Ou;EZ2#rrp&HOzPqNEkb!Z zz|6-68p(<{vY=F}_t5ODv4r7q-9d*wAY3^2=idJy_4Pb-um<^hoDP;C^z!>&ERpgF z{-C?r{B1SfyWkyN;}n|>&3Y6bqDx^^DIcRr7D#gnC=~b-U(pPQGSC#ra0+Zkfql9K zg0jwZk<9}0`OhAJ`nE2BBrz#>9HqO+cUoA078YC~T~wmL)uuq9(*j16avZQ@FxP#c zu8M&X)}g*HL|MRu0)MS`{YdS6ZYC@Pk#{GUc9uot^%fb;{Xnv9cl*l4v;`TA0CNi9K#z0S8PJr{OvB~k4yZ}V*M;V?5HXRa3t_Iq;iEt&U0|L+n4iz` zfDSoBwR4ABgS_q`~9jx_85C4;EM_lh4hIBATABqUh|l6+`jEI44>JCK<{bYejZ z5mOf?A{|+>Jr8DnK!32pzjSW^iVgXQti**@-r2sAE>uX&57+~es}kW~jadatpSPIl z0dtCzk#qtlUh}g6!C8R7^qGlTx=$R^^)t-``V52{)z^K}O}oD}b8{@+OGPUE?XdDL z@Ri6p%b0wGIpzMm-%UC~D|8V~m9{$PdrD+JHb=go=LP#BEYtQ>ewU-bg6|7O!8gkE z2N98i6agvA0VB{}o{5P`D(P?rOMo&v{`?0j4FB6T9W9|u%KCfw%K&Pua@d9>>j6X(!bNkw0$?(S2IPM<*D5NF)-k;ghj6<1K(Jy`S+Wd#`9eoigW-7Hh+XWUgyC1rd@(?zA6?q}M>x;6UC+0X?!BzXzW~ zVMbg4r2x=7g$_6lFrJ7%Ha^7{P|Hv`2H45mwvR>Xe>s9$NS(s2!);pu7JziDzzlbM zvJFv0VY1FV1>uej@uTX6ABfEc+@lDm?AOaA?%eU86TDOxZ06Vi3c(x4gRKicdSFwH z1*}#tt`@K!#UFnVsH~6A5d>n_Or#_%fORA+=fd`h5d%OEBmaVd2D}K2-5v#yu;I(7 zCm0ieCeS;HMb;*9SzHcO_n{8(Gkjc3?*oIZTx?`2!DsXX`TT08MaUG^WKEpFAccvU-+ zD!U@*5i@aX{VFOnCU@wEy4G8D*5cwwEig}`_Sh9^i9dC&?>heI=9T2;1Nfsfso{=G zP)z($iC0Ktv;gnZU1hrz<%n=WUB!^R8FG6WpanW{`fWVnZSxr$LW}J~zwaj=o(KPC2( zykn_kE>7jHp6H_n>O&NaoPeL*^Yzg8!DZ0*H{vH&{y|s%1tF0D+tv3+HS_BG%ZZoH zsN$;E&C*Sbfj=r%y|!Q-*dKK1hXacJA3Ozu@MdWFebOSHA_D-1UO$9AB7Aeb2^XNnIErETr!nPyCCf2>h&PiwMv1rCzwrlG zS<}%w|MNN7@a4q9&B%gwKH~EO$h*mGzt}P>tw%DHmFrvz!TtW=pO&xtlAN!s#3fd6 zhwt%y79 zTQ=r&9CR4d1sVN|&yduMR`BXHUz6VCS#P>B*t162mvE$B-g581EyVR8S2xXhc z^++US!i2j}S~je(-Df=x5kGSsj_O>r!v(n_2Y;Q}wqO_Nf1 z=K6>4VoXGuGr3ek6{de3$E0IvVife+H>xC$jE4~KN|ibir-w=*Cw%EI0xNN{+eerU z>q_;DJYw$<`Css8A#yDXjn+Qppw}lkQQl{~xj0t>gKLsm z5RTyNKx?caZe}`al}T5C0V1KEdrCGouo^;gI`84>9$;0rpEjek`Z1TqqN2@?#_}4F(eG&+)SM>h*L0UN>`jhnVO?Mk*;d z%iYMBG!Mt=PzcN)HJUzx2nnm;Ry!aPh7SwOb~-`eDoecqMJ;C>@pCr0v=ALaqay%4 z33s!QdGn=(P`)r*w)&a6mCU9y9T#RJNf~lAHEZ+|IZfsT9hzOnm$Ga)3BE51+c8?9 zefe({lgc?g;qWtmjJjK(*-HINcrVd$0f`n|>S{ z3FqDq|6iR;?m(`czM>ZMoj8`#M)>hK{787pB%`maS<5UKVuZ|w>KV?nkW;?wR;fG| z%?fbfbaNO&d}>i9+y{zbkAdnq5f&bsgJ@VHN>*zqPed+3l)k4K3>_IQNmkibElGte z)_R+I;%t_R4bq`7y#;W*!t^#uMs`5ZBU@v=VI6;SAo==16hu7vx?BMG*Ybt!K4T=f z(Ijyqhb~_ig!~&r`k_#ROri=1bXIhbdoaYBw|jJ5kS}Yt$iDbAYAtvdMYiIGk*1l0 zeC_7lQ?IBeUK-CvUHr{GLfXi}kAl=*mqYAVM%Lm7 zC*IhOPkS#FB|4#m$n`ccbO?^2{?4a)q+FN(Y2=z0aN6r=Ko69=k!u}s#xsiATZmHo z163!H+`8mCQB>i<XhdPq+{DQQaD{J>H65V zy@b=&53>n5A;0fv}hr}5o*a}VQDg3>D0I6m5x7+~9A5U`epO;xQf)Cz*G4#|uwR#7k`+H15D%W2ut zR3+2N*e!FnR%|mWkiJngPFMZu;(`1bUN;E0C?VlAbaxAJI$~(7&IfAEkY|7ZGtVhk?@ig3gDCM%p_s@@2uuw2wX>~va@Eh6Zis$HE^Pqh`ae%m zKkuQK!~fxnT=b~@A4(dWAT&`O#}7(Z*P1KdpJwBFV>VwshZoHjslpSEjwUapsxN={ z@B*S6H7}rY3@7BPp68JcSj=!a!DGdb5fvT7362%dHdcJm8Jd=!(-%iL{_q^N4L?Ul zfJ6?`4@S=+b<*=yw58rZlN=|ry0Z}sHFKVsCzZA3cbV0lfbWJ`eex(GnT$v3ge)2= zjLOyyS|5-BFrWJ!YFOrTE2$BfoNWu-DVfi;g-SS#`CN(%=R>yu&e;aeAHkFc&b#s5 zz>GDJ_c$1|?)h9VvAPGH&o$1f#lP|9bElzLWX%6~zd7;}%fF#_Z2m1y0sjtwiuUkt zM`o2dH2z&i<5}l=#3U%RLs*X}Xmv>ZI|#-8ul#!o+R*mW&7mUa5g0+_@sdh)oDesw zu}811n{0JL^I{2MF41F0X%&r>$Vpy)85%o5jAY6KkCGQk^~-3y6yba{4y!Oo&U2&b zso4_2Z=X@1bwj{W25;@ikjp->(#Qb0Y84cRts1deKK1_DCT8D4Y&+wb;DeZW{g$v> z97=4w{xMVWF`?>Pb#f*UAoijU?L`z8O12F2pa5qM#A~3#kVfT;z4PQuw1UWX*l0e8 zF98ghT?1G&Y(|<}UO9n7(EQ~hM?5``6`JmF0EZ1lDd%$1RzzK22Yz3fT#;Qecc49Y z#*u@3@DOUU4TRDaAAz*WUzzw}P)3Y60Lmsn5M|X8`c_{~ijO)bn|I1r>q$ruE5U3n zGcuV6fech(;1Q+;qM=Z9HfU$xJT@ZlKKot)u8% zbA_6dWmEZo;6f7)_&J@Jw=>LK!1mW84e;4`l%#)TH!ZXNPu@4jyswIRuZ(%;eFxt9 zxcoTk{psj;&2LUJ6Rhz=57tU+?`Qy7<`5>1UugSdmXw6|HN_}g@VStA|5+34@6ie2tnW&kl z=n1Elc$&ypdN&w?MQW*xXbu|jm(4+)Oa=62%a^YdV(xOdcox+vj2dJu*^eiW8~ETF z^ZeyV{$^jMmrsP6dc94()!-EGxG))Z$Av&m{LF}x^gwu+OvSKWwGTU1suR|8t>S zH~>&487N!0P=12%2Ff?(QTu;*6mF?Kf2n})DE^G(fv;%4l6BM1}mW%GeQ$^`XX27eS!4Exwc z*ZPCs!O_aug^T^xQXQu9uEu=S#?@(a66N|sMEa*AjZ4uv#yORUQsy2ETbOMy1V3+O z@wX%|)Wr@~Q?DbI9T%$=FCy=20&A58kWntq3g2>glO!=T8GPi}?Z_v^X{eLP8Q=$Q zM_zioCUTaGZ6=S*wkbSE4M7PT*ZfQSc);~lh@G&o+oIWzJVpAl^%yl#dm`4emSVT> zoY!Z(e9Qu>vk5s*OaRcMHzDI-=wwX(c3O7Q8MsN>)XpjFRJZJqLidmYf z_K;mXQBYbuIqu`$M4F4T&)Hgax)Tq}?Ynsn-LnFDzu;zDH#jw z2t}rIMcOYE(T1szW%sv{l?K&{ztaA;S!(C{i*PK+533Wq4Er5?*SZ(qo`Pztdkf88 zd@DRoQ~y8`;E{=W;H50YqqD-Xkp!eyn4ms+p7f%Iw*5s-H2Cqt_;HAqu70G=wii88 zW3}E(&Et_xO$DY5=p6}qg#mpcIMRUL$_4!d7jz#UDf(vf8FG`5)vT9A~+D_uj+V&&YM5n_dX$dK>5(yU@Lg?*_W3{+# z2R^2f=6W#Cr$p~(th@(Qu;&BE-;0+r?ux4qfHUuWpdDecF*VL*lVe?2j&xy3z~e#Z z167ae`M{|%12Xf0)9_;E19K2LTjzY>q^IopK;LT22deQ(MUomj9~l0W!HPQ{`29fh zfj1x+A~tbs-K}DmV}+(xs z{KIO!TzGDc+62h_B^PEU;B0qzG}g(ssevAyhyL)nCvg$0^%|^V zG)qwLqFz-KC*DZNWnoeTRe`D7nZ~Y^9j_M`0hMwlm-LhT(}(SNX6Bt=JpLJiy+>NJ zZfV=TD)qN@lj$eQQl~$--5>m&u~+j8a+dfjB4-7H<^G)C{T1at%l9E7JaIAYZ)aIK zO9J?x@57Z09l`fOe%~F-+#hjtGY~`(#C(6aSbj%~62qG#hoadAFKb9=y>csuXG5?S zu!P^T_^l$kGkN2TgpjS)2T9-~sy1KZCQsrH7On#d5w0d{t?<6F#BWfeuSh2_6Jvhv{)tn8p)WZq5*^$efL^&^yb7{_-;IR zPK>hoPd@*VXi=-OR5DzkkgR14gtxGe<^2-<9WdVLMHjZH1$D(aH15V->V98%c z01T89FZE~pM&3v znx|QbYj`>~XOC5};3O;Ych4{rE5NSyD8c8D6f0*zBpuEX&pj|t^F|TeDs?f=_&`jk z*%GVNsHtRG%^&pHfr7Cd*)#`(72D3@!PUXmEN4JD0OBXWM5OK}Dzyya4PZDJ#X87H z`IN+n?8N!@J{IIl>ef+@XV2LX+6+iZ9I0XUj6`&bm`QZXk*?HK+%84kK2b4}3!&2c zU>LD_3s#SI@?>xkf;?Zj3&tSa>baM}<>1iiiyrR1b=8MG@e|Fd$ag^HS|2fw$@;hJo?={ba8`2s7r*Q2s>s3VYNXd&>f@xfuNVo`_RDy^LDk*5HN zy*^O#SH_52t!WM`1oyCZ8FF^dCg|)GJCdf5B=n?&6l?jqS1M@z#qp-Pfi^8N;j`I2Avr6f-ojK%K34L)F+Br}Iqu9U}X&|2kReMu^xP~L=AocTDpE=m*53&B9 zhI$;!wZ8g&$btLci{cS(IjdQT(ZT?ckoBeD?#Ry&7D|Yg53aXrm*OwD6f}51)VSwk zT#9G#UAq)Ibk+4MSf4J%Sd`#@;%D$5<5w8D>ZJXNY&1+)LM;3c9npf&58GE@suU;L z7MF9`qB@nq=!h=GAsB7)8O1Lq|1+K1DBKk$e^2s=+o91KL7}D5Hqg*}EdQaJe2&z^ zVtus9CteJfIr`JJ_wPj6`=9aVpK6;&=r#frW$)9iS&ov|_Wr)+snC-317~tuAaOQ4 z+Gf^NkG)TBF6Mrpm4hn|7N683+z7`*_eM?xTe|Rg?fRWZ+jd<|`G`i{SaO7G5A~o0 zCz-{X=d21OMj&yR^1u^046+vEOT+ zIk5fSh+2^{R5A7&*rxrKqrtd*YxzQtW`{4~`|x)%5L_LPkKy2<0xnmCc`9*H1?{y< z%HVc-W3<udtHgPjsZP)I>u1*B&L`je-cbt zl=(S`WFca?*`nS5;>)qhpGGBM?rWw$*%v+s?TijvN$GjB@M8}G72nQ? zKY2N7MJ|Hus`S5I9Ed*Vbenh<-%J`F_$3IAqpFkaG{HSHd|5u#x zV;dJKGC|yZk4Bjx=tl9>bkd|UFaX%(CzJ_w#hx#=*zHtJ$&#uG9{l~S;^b)>c99jG zqiHR3G;R0fXyU-(B0BTFm)KQ_$-(K!BeY=E^UzC}JE_8Y8V|S8PY9c$n9cDCO77{dy`-i zAbjB2gXhMi{kQkl=^ry4emBI?=nT)eV9_IXrJ3zv(Inb2=SH3y8$SU9@Rk0Je;K}# zKb2Vn82~X<;+%dR1cz-Pf}MbfT|aT$47S3+Ev$4Vs4|YGL^2?c2W z;JYwY`nLhxbOaOVjNHN1sGot&8ejkitz|JGOWnH9Dc%;X?8HJWTj}bya;%=)s(~rQ z9c!vwRAMC7|G+c7%>v4kY$$A5%>>YaOIzx55xJnG~sxa15b=tRU1P2{2 z!xuqH=g>vSpf?=3Ks)LDVVWsv$Lh=Aup-_*7D!fxHRkKvG^>@e(UX>hpNz-2(IM4;eS zO59SVHrF7dU$8ux)ry{*`=B#0C%AdY=rVJH@bzJ{kR4Vv4P=# z)zq+BK3Qga)wk44`acIA*h7yx0;}Wb}pvMz+?5f8~cMvNraNrp71Sw|jf7j28 zcs%)K{fsF&^eOJIvW8ATZ<1r4n zWNsF&!9@Hl#4!u@9{H0Ofo0#vnWZQjv2qbv*4PNiNOJ^AJ2oO;GSayDlRHK+YBdZg z;8;Haa8Nxasjc`D8H{l`@k!cLe5YR9=`hNHO~u>bL-)dL@lWJ_I-O5hu23o2(l?4W z7NW8!?eI$Tv+@6dRtPEcMq>X2(Nz%iEOisyz5?9fi%9q23Q4x`HLz3=x(LNnvaY6q z$%nStg^vMXLluL=10gOU&+i<{N8GuE%t~$&NKbyExOpHuob4Nb0wMGK<_ErH z+a?fP6$mnQ4Hoj%_xqrZkjRFUs#Mq!WW6k!=no|ZLVX8=rPho$<_PhI;gs+2#^hDu zl)L{%+{H3~-W*@)bzlfgyaK_+ooki@SR%?@FdBiz(JevBijcf zvV!0agNdvIXNmu|AW|+A?g^YCGjK4}WY;=IS#zS}S0N1HlSt+MV2=LXL!Z_v_B0JX1NL1oYEbSWey=|j|4 zome#7;$`xv)>j?zCBDAexy?cT;p!^~A1%?*0q~J3vT|_!J$RRckH(v0@Nw({4?g-4 zA3ad|5b<#qi-uc_1=|{Yw8fYB_}H}dU*p5}`GFUiWx@GwTNKMGgKbvvl`?ld4uM{W zBA#@A4*-DJbbr4hr=?c#MPiB??{5W`4YwU$N|7vwnz#_FBJ=@u)<0$hV=7>KK7R*w zVL0V76z8k}D*)Inh59ndhtEQza_fO=CX&H~a3`Wc?k__PZ;C`6|IF{wq`_ zS0utIe}Cc@UoZ0R6km!$l4ke&>5V`*r1qOdLWFbwUdk!OW>t;?Sb>C98vv->#C|7; zzhm7dgzK+WX{xsE%Wa>Bg5l4q3jCLqQitglv~V1%pU;*W@OpTxS*hpc!HjV;mdBuu47 zPw_X=x1WS_bI>I9xMc4YYpHIeI|10CUQrmc*-K+{=Uo!5x~Z@`u@XVT8h=W^2dJ#! zA3#~3C(xjXMHRivXkn%fov(iT4VCm9H#!GstQH>j9Lhfxt$w5ZxtehY)CUSbABukL zyTj23e=-lRS3lOD>(K{d_AWr_L(~T?Sv1^YAmPyZu?4=w*9XfZ4q6UZKQdkfIsVKB zfWB5F-T~AwW^6)j_-KEhtR+2NJp`2vu6u1PMVx8bpY6$1;K`ZTAF9vE++H$Xxa~tI zfS3+Ea>-xzp4By|s|C6no^!j#Kn_r|9Aj_DfHyJ{GFnqNz^N zZBU=O)z9prRZ&HMlmfUrA|k;okoo0AHkJ{@4_~Lgp-P5qO?nQi4B?cvh;F64!ndX+ zhTH2b6g;OT(|cSN{uCD(+QV<4T^&D)OD*#3Sz4jCL}OL;_D zIE3`I*4nV*eN;(*Qm<6 z>IpCcZqdInnbRp^H{uBiXz#rgD?j1f??&6~y5@baMM8=1@d0Go$nUUEv{p-Cq>?kW z-O`!Rx!60Qi+-Vsy_#vpjrt1zJ)w=`Zi@n2D=88O-*ZrjA1&!Wzshj2yUg zp@8m1LQHa5x@rhx!E-&~l$KaGqJ7(q8;g08imAtN{|R)aW~_C1(j`+&$sf6^=#*US zl>FQ&dE087cSf6YxE78K{46|Qy)M5a1MuGIO*M(v0#P7wMWyx?CZPa)1)S8u06f0J zNp_qE@2+7U;Hpi)pY1QCIeCD;MIeWbkpBynOcvlS=t0vmIt#F?lZtuD zz{@)71GMUhwmi7874erxMhQ2(L6QRVNDxImh-L(%Oh}j!2<2{r*~8f=Ucr;4 zM*U7KBYkR$?`_hffw~#_qa+48%t5HfC2}*TJ2+Sv|)n=o9 zamA&RU|BGu!W$tKk}n7&RI>zzYTXcgAN4_;iu4@EV_3R@02**nhekBcy0fA`5uCV_4sjlDo~(t*+5NUG5UkGP}pNb zTnuz|;0q#l!GW)hq@QvJ)CR@$fR1)54E9zyG6rRTVcw`KY~T!1FtC}M;Wh?-g9`BC zcp-{KW8ke(f`Ol)0=Tmne;~p0b++7C-US+Yk7_ipI#Xx5VzBk|SVBn1H(eRyt zVtPPnPKEK_3JqgWwikL^S6D~&FBoWrm1GA43f$?%zycIA7%=whMTCrl2Tq=^G4Nk$ zr&^7HlL0YrUk3(WX0f;!xM*Em40LkfoA2@&*E?=v(8K9_c?)#!i(&=? z47-LxeQ3B~;6ykTl!8yCooaROqk@6CbztBH7K@94Ehc z9N|=0|C$G7NiitfiD{#*@I5Dx!UHYe_wc~R7_pg+Vg>_dIbdwDV4$Pnfsdt~YBdUb zP>_O$>cGHrEEX37`9FA#$>U=BJjUd@Ix99nMH!3OU&n|f2tZurIM0iTNL4iIBA6*^ zOf2S{Q83ZWTVa7DopQNts#D=br$Xj?9@M=VBX19)m_c1*%{;k9g1X-@(;@Q=mv*Yv ze{i7(z+G1d>aJz6xTs568y5rrD0@J?aIKHS+?#)k#=vG2(->IfR5-_5VSWtCzHlnM z;8f@`%OgIo$H-e5iWv;F&=@##m|)vdq@Y8H!&fuuEYG4MC1 zc|y{=U-4kzmq;`QenByffv=qkJ-rp?#-Qvor^0hig@oB247?U2ZzU)e%>yUjEf{F= ziN?U)(oVIyAS4*Ltqu(IXR#Oz(53oxqU}=cT^$z_JASn>@r-w1YgIHR)}olkM1@mf zgtx-<7}UMvRG8>g=<~Ek8lH_&RijYMV1l8HP^AqJaSmfL;$w}8!O~8(%EncdkcQvu zz(h|Li-U>0=WI+=uZoKa#VMNb#7{4Kcw*`1XiO}%XIbjMPKEz@D|{4#x|f{_fnKy2_Lom?i zLyds}(oVI?oFEwJSO*5WvshdVRIP}MfuA{_6dpLiJD^3sL}Or)J+o7vIu-W5=t0?g zF(`Y%sqlbPVemU19tg!KlG{+sVBkp21K%Sa8q(0j@IXIlr&^tYs0K(wQXLrR%3^Ub z5cw`H1~y{mr7_UnJD{(Bj>f>(_5@9R>{K|;Tj8A;ls)HE80%E%^P&d>xL7yJy}kv- z3y>#o}UM^YXYDSdW>P#z3FR9+G~wDH^`7 z>g1D#kbE(U&C78e6QVdkYVQ1pTa1C<-2F;Hnw(A4`*g)6)j-iSfjQ%;5ZoC;lV zlF#u#%3>79O(F>6B{7QQ1{5TRdO zquvUy#-QwRr@~05!u^~a*$WNhVid=9C}uEVyyQ0r3kL4_Kx3d#+NoCQ4+sV}-~d{@ zrItJvi;IDEi{oNoHD+EK1K&RD!9c~jXbe=?6EyXvQ{jDYg_mPcHqogt+^H~wlOr1g zV`3D?wJ2sVU^L<6n*{??j5WMe+NoB@juQ;LSqBErXR#Oz&_{d}npgV;E5413iElBH z(wIp04(z`_MPuT>_IyiCb}DSbohHt}o{B-;c&EY;r@|*Mc`#8NqdX;Ug#$6RI5f~1ru%SkcPG_7J~_mf#r+hV&EH0q%;Oj@eb(oAEPnwxjo-f&pQ2D(T))#`{bf`OfN@WPoa z78e6cze$oLZn_twB9h%1%%0^;Hxy8U?(ldhAbx7ke=P2E_l1H2max{1mo_*Il%PJt z^o#4s_uYR$dQ4`qxX^S3G~#PU&*3kWb~%Ut1gAnBY_<9!njWp}*`GSnsqmP$LUIfN z|M=c?r`F&}Q@s3B4?Q-;2=g2iGxRVEK;PdW^cZdE@rkrkt?n6pKzcmSVsSCh`RlkC zxWIvL$&(%ooVqp|1E->x_SG9Y6+ZM9hfGX0U5_LK@aFPqii;y07?4xeKMfm z>R_;iEEab_Ll?#!&@Iv~N!rp2-{otf2Xr}#=>fHOD*Q9agMpkF44jN&WpmFwWRNd@UDDYMo z7=wXAr^0zog}@7F)YciDVw6lv6f+nwUQ~F(|v#sc^1S;kVB{80Z)y)-6!X zV8GaoV+INay1uS?ppmpwt-c#27`VL-rv4MN#`qX`zA`QbCb5EG;GM@k7#O`O8Uv&4 z*{Hg~sSxy57!ZT9fK%aYr@~ZjliNJdAx5lEL@|Q_V>cRJD;U^oFmQylQ>~Vb6bzhG z2L^Ux))*fH&(4pFfyY@vFtFe;4+idC8I6H^?b)ch&Z+RGw?e-dl=W~bWIGj}dfCGR zc`$=(5t_%0d)Bkb9zy4tC*=}`~L`oy5@ zBBw$dr^1dWJyMVxqd1zOn8ARt1y%h811;ay7)X|Ou>XxndhkG59T?b(Sz~+*JTWIO z1|DVw!N4ir0TnNg#z3(>8&&&2I@&W)oNjpVBnHEFi?eAV|)xeHaji`9%2Q-K>i~hk`7xIje%kIY*bz4RA}j~ zaCr>Mx;PcGoC-gAr64CpaWp}(Xbh~nN-!|TNI`_*Z6AUz|0|ODv8slT&(Tcbj zn7|5xfosAZ4BWLe8UuIPvr%=WQ=y}`LSYQb@|_B&ITbE^!@~n-#V8IRiWv-;8TF#R zf`La25B!5^AYHuqcMAqqAy-wr8TDosi|gWr%j05TytGRf@4kmU7`SsuGzRXpXQ8UM zQ=z}N!lf}N`;Sv0)2VO-*F|k9Xd9zA8lzY=2IgET82J7_Vr)DNcpHNGa!F;LI4s zaU_Zv3>>A`m8SI(44j8+O1byA4-+q9VD?bKzzua^U=xeQ#XxXoTns!Q?Ggi1AM#+} zwr`^`aGO2vQ$3vuPkSr$h(Xx}PK6Ao!g;uX!ofi67{$>L#S8|FE%>mvU|{Df8UuSU z@gfGw?-UHQt^)%bSS&6E9{iudfCMjL1}$?~H5L_gc-_jN4#c}-kqNJp=%u0dgirNx zO3pAPdjU-nTPGzia!R&wN}i2*CIj&E|=Bu<$7G z9~DlAb2w@Tdkm+XsyBjlP%(mc|3CJ=1UjlBdp|%TEvPT3NStwrP9uq;CJuqLfF?rZ zHFh8)TU^k%fVhI%0hGmHIzrpF#sy_&T==OdE;BGAAUG}w5CX0N2Sr@K1*v8WqCh|; z|L?2!y7QLaL}1SOA94=#d-baBy>;)cTeqriRWaW1oeWMfoCffSRK0%?>f>LcN1NV& z^NT8w;`V>%<_=pw0#0zN&CQ4q!yyO$KG-smRzi?kkg}L4>(vW)u16x-SgnjmwQ!&r z39r;Pw)50txet@^r4{J%u}?7wT!4ByA{!$c0yd(iXH7SRm&&a!SEB%#D8rU}Tf&yl zH-#gx1< z30V#O6fGTl@yq1(BiKC#Vj$6lZ2*rO^GifG3~IQ^z6_NwAac2vPKItH&U3sxws{?9 zyVALBL>$3W4E5ad_FPnT)Z1D3M2k8DZbA6#PgEuzCD$l^z^FlkBbH%Pb2!H;<_P9p zf}K+={P&w%KE1Ae`s^9)Q-$HvllbHgbaN8$j26JRMgV&!y`7Cb1nB@hn{U(Nq~#4? zk`-V}wd?}6@j9;(utSEVQcPU{P$SRV!!LLe5ta94I zbj?lLVFF{Q)BfG^Vz9@S@oZFF5f++<-76w*AuwQ*H7L}tIN(rCyX+ueKIvh(jK__( z7f@~k#Qp9k0h*j*Km$Hr+XJZIr?BdE#`encfqvLezgVsOrpY@Hn+@v*sVjfQR<(z;i3=rAXd0ueh%qW} z4`lf`XMqQ-h!oUF#wW*szx|^4@VD=a4}a!@`0&RhfREU}xbzn!fZtRYpZ=T#@Ym0e zPk%)M_zhB*V)MTu0enXS_+t{l&rATnx*$G((i6b1nHQfw842Le{UyG9%M!q!nMnT= zz)wj4KT-SI{&Rf#XTBDnKXWgL&;RNK@Cy>a&rbk9CjtER|Bg?8P4D>hH|&kipNa(V z)dcXzB!Hip0DeQS`1Ipa*0}bk=3nvoQaAD;kzP6GJr=ftN!B?0_~-SPQTkpTWQSA71ANdP}H z0sQm?@Y55(ulZ+u{>;daPybxGHzs!fSe5{OW&-#n3E-zBfIquOeEPTVj4$7r|A^0@ zoCNTz6TmM>0Dock`1I!_fWQ9L`1BVffZu?K`*`-PA_4s6dGYBVlK_5Z0{A5f;HM{m zU-L?Q{;cU1pZ>XWS4+^C^p_-npOOH6Rs#6j<))O_@}2o&eE!TmH$MH<3E&qbfS;cLeog}T z>tBdZf6Y1Z>2Hu*R$}v~LWi#&l=+l@!@B*p`IJl<;3el%{PIMWPpQCBr&fv*Os&kC zrEdg}JHI01!wmRth>34?f~ktUWbjqXzz_qz-Bj?X^&61@U)}Rb)UReSrXIEVDUF{~ zW8&MHV5(tAQut)phoOF*V&daD)$v@D4bLS}zcMSn7un#V^0Q+CeC0{uTV&y9kC^yo zC7Ak1O9o%HjJYuIvz`q;YW?yO;G6zz68zl$j-`IXV&bbyFm=&68GL2kp#(<#+<11Z z`VC2dZ~p%!QNJ=7kYV8GxS04hB$#?AN(Nu%+ZKMdjft;30lw8Ulc-;{47V`U?}@Il z_=&>|yM$>3|4Z>e9GnE0@i78l>nXOgI2jSQwR)bI7PV%4v60(@1;;48D@>lYK> z`~*`O9h1SgoeMC=_VDSMvFcZp0N<>qli+8y49zg`^M;uCRwtN>$V&!a&A%=6+npV& zej^g#t9vSm`qjw53q$>;#>BTX!BoYNWbn=9#ZpH3b&83vDgnL?PbN{nbZh-y%!3`^#ZteGXT+-CkOcVV|1F97Wm@YuE+)PW38pTJlEGKlvz|zTpJn%1>UTp-e5(^oW#lD; zZ~aS__OSc(SoIr`0AJnXNz|_zqb!p>n;H|}&ID5tLz2Nacdn&=onqpvN`P;}^d#z6 z1=6#J!0aUm0;>6Eg5{Syaf2BKb8bP%Vg}a z!JZ9^iLXwc0TO#^sdF;;GOhL7cxtTr4M~7+{-a6MuUa05WT@Y`nD{oxlRaYBuP7OO zCHGq7*ES};@&x!+KaxcKau!(H-xH_A;%8a{d?S*zb-NHO_%3{#OCMDhm)vZ zfjn`*Aivj7j#aY!lVa7cC;`4%)k*Mk z{XLfY-4GMsYI(*-?E2*;gD>U(EcM&{$5{0nkpN%aLrK(craaQZpf6Kn;@c@t@Q7W% zA<5vY@h~>jfUi?bd{qhXZFn$=`sK(S_Xd0~{vj4WJ0`$ao(#T*Nfv$S5fk4mc^*h? zex@aZZ~Y%E{cZh;vFewX0N?ar68ucJ)^Au$e0B1)kJ$C=oD9CX6D|DQm=UXfLlWSd z|3DJ;Yk1z${>H_`w?Ure5xagx$>3W*!BW4rG4Yirz_&V(MEz#|-BQ0NI>q8=S^|6{ zlEIg5tzVaz_@>K~L1OcB=lx04ui$T%`n`Taton6MfUhbke0N*q*Dof%`SQGv*!Am} z48HYpAGE=qecCZr{fZLco8?b}pJVQ_)bECv_*To)JYv@`FByDuw_4=4`}kP(8<7BC z-Lxd?H*>tDep6%O+bIwGiCw=T$>7^=?TQ9Xp6AmR_m9oa-xVA(`vzVEFWy(sPn*-p1qyC39N zq(r|Tpd2JEV$CZkLl#c zCUl)i^5Y|B4W3+0w^ifE2S|y|kIH+0KR=q`&&A<~pu3j@Kbp{a>ip=9;~Y%w<%KH@ z{NPQKVl8qf$&YuK7558~4vimgAtgFL?wqpU{4m}(_9J$RVZRr)!g2SY+!Kbqu=ioP z9alYHP5;nehpn#K?aot?1)D@mkX#(NHANskfp@no#5;CWhVK^&$y~i(mbrR}yBQkf z`ck{J*3AbM`y1V^FBIQ}^5HAR*PS~aU2BxbYPgqm+BB=9~a3yhsotyXhWFaZ^GpZ%;DcmgtI!DscR-@Kt8Nfd_C}>x~_CO6kk`F zwnp*&Nv4HW6Pqd{s@aWSDwnDT)f`#P{jO`!wNoGRb$yr(@ne5#X3Ndv6~m|zyuJ%t zTC=#}TS$KZ-D;M+Z?lFKplW)MCmUNw_ckk4%Vn)5wmMd=UxxY6X7LaZ0ikx2-^+0B zJbu}P9iocf1-1$~bx9K8VLL+rp&jEE6D4wFV!_OGDhxRZ9%*!LwL60pMFL=Y=m> z`n{!@^4`)_w#IfM@}sdGOAygYylN|flh0W~?uCs~Lb>l6sg-wZRVz0gi`$)#Rb4xj z$|I48Qmyf0-cTH^eV5{Xvw7*3u2gNgpK>AgsH6AzE3zQxAHcifKv!q@PRVxK*Y+|Q zJE;33bOyNxF}I1Y$n9F^9ROqFbxUjFy@)^Q-k$u)@OE>%)_Bit>F!`IBR1t~Fb6ipc=@ol zZ(i!x=7wshWe6vxu_9lMZE%&XAaF>a5d^$GfIR9|_i){ZrTWjlTdg(Sg}*3Fc@l~!2+1Fhm_ zmI?M=io=z(w!9cmdRxvE!zDygc#5QuSzkg1alB67I<{!sGkBAYzBm73qpy+OEg355 z8y1Z|tiYOhDc)n5VDD}b`p#>iFGIW~A;KJnhD%$+tS6E_FFsJ}r)c!0-EO1rBolp4 zP~Zbi^ev(8Tlt4A{$~2l(&&30=d@`2dlpX-TX?cgUwY_C^wL^Cvj`c)eNnP_{arN3*r4|}6DCWd~baT3~Ruy;j!atyv>Td@@> zp{|k?AG-|*pi#_5B4znKgIt4Ye;Hw;r^G~$bY&}1sD)R5j7Cq;O3&4jp3c>v2YBV} zj8~E%FA#^%nE*W*Iz9OsJty8~qo-zokw0=;;KzwA^o+Z}rdP~kv?1bSKkcbEfk&sz5&;%WM5QVhzx})T1r)GrTj5V;cRhxsa;yB#+aq9k5;M#5(I^A z{FodP`XiC~uTzh-I3ecwKnFf2GoH`7o9)m00oq>iR4TUNKJ`y7VFGsdz)^R+2qhNn zkedxp9;#Fz6+JS3EH}>wHr|D-xIf$>bsX1;imgbAKAwN>_J~&Q_k3V;+qnD?_?!$s zGC*W_{%f>_AIth1_%Vn4*gHOHeyn3w%x{nmef|q6(fRS{nBUKjbw|YI$8vm5h93?P zsq^FHN((<0xDEVxj{Mj(E@^&z#H_)S3rL4P|Amz3{HPqg-~6!7_mrB(=WyJ`<5+Ho zF&>>pLR#i~)}aHz+(#Lb<$TXGh*Cj+mXCLBzDEw%b7ii)Gm(mq?Pccf;j#Qg&42zi z6(2E{zm;)3a1b5h&$I`4N*68~YdmF$3t~w^PWHJN2{`2C@zM`#7ITqEy56Az1k+YNVWX?mM3U`@oRI4-ytdVBHMuG%50AKI>%5-x_|Ss;h9c@m~?Z* z3YdMDw=BK&o%dr3;=X8|``Eu*vMQChNQRv)&VrBO4%{Bi^*9>Dq0|wiF{%^MFIp*_ zamiAn@WYNh+Wdlj{%{wU&oN)P4`Bje_03y9|%#IbS#%T|e8wRhmUvq+1ljd|^o+P5-p{ zLOF$8Nq<3~(^95>($WrE<_mw7H8!-*t5K$)7n$0{N5~7g)$Ai#7>^twK0rz+pZL%M zIE(hV{t&tl%n_4EMbzIsbGF)|(|10Hvd`#hY0mnZC96{T9q>UVzr;_H`Z$uy^U%lJ z5ca_N?6J^K&Sy`S`q_Kxukzs4u;!IYdz5w;QmJ`2GM0xfWiahQBoo=Hrmonw8_r6kAb#veXV+0Y|_&%$Qx-l|NdF0C)FuV zkw8+NAtzDmD%%iN*&M3udn1xnS>qZ^B9-O-eyA+FdDHMn>ThIx^btrk`FiH@(L#Jq z%KyEU{FURQv}qKwVSKazOFEJ9kreu-TQs33F+Tc;S%W9%kPhAdMM@0+_j+W~x|!cP zKGOW(@cFaR)~6o-k_~N%c={{;)`=I^Y3=%-2vdQ9`gAV|Om(6`AHn{H<3MyU%aP2v z=^3yFSiTZv+~gy>l#$%dBpdE6+_)th?zNDWoG13Uwt7p8U0b|^JUkz^xY+fpS1opJ z^Y-z$ZgD!iz4(*v&Erpo_Z*K)=Su1CrKhsjHD3af3dF^toX}Us86y zQ6Hazu8T+6^(U#1f4YcVhXy z@Lx@S|6HxfZvo^<`}8*C3Hh}JVNvW;({@dMv!%3`i80)yBjqayP_zm|9^`+V*9t0rsE55%KW%QdK%1L& zX_F=Aac=hP=yS6jqRq_~Xz?|hS26x!#a6PN^Rc7Rg-7Ba#%SK{d-m1JW6!hA$I3|6 z0(t-txgq{x0*9m4`PfR!7*&sdVCi7*o}fmHe{e~cq!If&iDw#!LL83L9@HvSMnF}k+NO)z5z@!#6y+UQ!-)kv4LL+uT8jq7it=^bmk-+Z}&rpG1A zBux(SHNKErj?fvp4Jg3CAMwi+=C06vtQ zv=Vk3fUWE-ffEy>mWW{kPSI>YrxqK~(*E84*#7ew?*R?Q_vgIN-#XF#1I-Rx@F81u z-V+;9Q~P|=G$hl1&Vu!z|GfPxmIedGu65c|y7+|~tB{r_1nWsp*wA-z3NPub~k2o$=51bp!K!(-X)Gzh?aRR4qB*G#x1x|Lf9{h`Rj7`KHAF z^T-wC@)B{|yZo&aV)i0-;1(?u^`k{JKM zlm6exFJqWVer^U5n&nrMWs~2J?4gsC->1k6`DJa0$Zs`LEb@C~z`>K>3-y}({<&O} z-vY=}j(;Fe$ZzU*QRO!t$&}x0DQ%2@FA$mVZ^O#&T!bWmrqdz-?pIs(+mr1!L-~@vi0b7FojkA)m|8IU;?rJTrv18#aoq7{efG z4nNmI6nK74F6Er$6t`FGYu@~Xc(=ciK-oVI)+n5^B@cp-6iT$Dc&j*?XYDx9k(T0eQiYQC~@-W!&%eND6%gfrb}o){4{I_^f$L2|CPw z!cTj-Mc)dC!42dUk8;Xl%9c^6DMgsYd;YRa^5s%EH;VK-r$)3@7SyP<>r>UBr)_~6 zXeGtTO;xg(CwM*ED4c07VGZfcEB!X|y-eh@>Op+L9gZq5JH<-uV`xIxuDaGL zRrAPn#%$tOX3m$=tlFyh=79!Yl!`4z*y^PQdO5{VlrHqIQ-ddcDf0(X+t8?VQUkbj z?`ZLF;B(zS4WAU>&m=H_((llcsKHmA)kydFzZvO&(^-K>NK`g{JR*6QxO^~5SH3B; zq5L2@n0wPXgIR-l-LN-MblLlBbLf107>W4}4PfCyX{h|)0K~|?exI{~`JpWBjo`e; ztHB7Um5A#SU+jUYVwEcCEr-#1aZTwh&H_*Hc_$kO*`(qsNN^M^D!}#*>_1bnc|VdL z`V)aW_6)%F5pLvjhE|xDRI;ubgxd)L6z-vTG$VG(5Y32D>=7fTRP{;)4O5mko-(GrF_~vX_)^b{f{{-Kp>hg+8GIY6)X&O2Y9J%BdY@!b?wY6X)Bvrc_+X)cR!WJ zS!<&bp6ndd;9zIjNWGPAo(DhUZJTwNCousBjY5%8Qi`uX0|8q&|AY&iKXw;5pNkLr zJi=}m0OHXqUP>A1g(I(^GBPzGYIZZI`39RWgEr#fzomL zjL*M7cTl|#_*|f+*COCW^>=lOf@h?&^DlnuhSe$Lo5klQgbi+fN_<@iqBMGfXMguO z8QCbFycoZM-gU3=b=zF4h88WmBt#izG5a``G-f^JO1I3!vOvPLXG^HCbYNh{Hd?V z09E|MV@deYpQCiBs6Zlq*x4%`rqP+h8(ney6yS$m6|^eLgANT&9_5ngA34mC4m@=l zQ_LiCR|%A6H+*{hazy$C9*c5U z7H44?J@SNxqqGspiLCUJh<#B5U_-0rxvMsRDjF^T%;52D)nF>_YTwskY)B1WggwVY zvG;fz_Z^RY4f*DTNteG;-{aKb$$peK>(DDhufsLrI*gHRO|L_DOC2zS6tfN;WgT=s zB2541B0T~9pWOL6=zq;de<2314XXd$Vl2%;)qhg`%Q&2c(b-~I3P8)_$Q({)X^AKV z`6HFlaC+ou9YaGU8us<<`$exR9xjSX@8v!Ao8G>X-c(Joc=dFI-l|^7&^u+zH0Xj!@5h^1 z{8itmBzO8(bdoi%Tj%n4a5?@za=naPA0HY0l^+8?mqck!?h^54PrV|#)==ML@;^a+ zRB=r7`t-xW*3ndE|LW6M)<>6$wLV+Uk5Zq(s{d2yJ(PYm)D=Ul*3BhY2=&sVlUKcO z6vty$Kn)bZS@<5>++30a-Z^B)X0~aPKZB)74<5Zv{Tb7AfE(=ien+nHYy^0$xa4hn z&JyUF1#1~hQ*gcr+Ylx!Mykqf%lTie+b{?O5HV23em z;x>}i03c@q2=SvF5eAR;g9<`wke~y3cLBMC46=s6JzRv*4WTdApx+-Cde;`{s7H5z z!Wz>s9m5)lA=vTHFxSLTF9`<~fWAb7-Zd`t0Rt_xzokQ7uhYKa*%sQLKBa~Br5f}P zVJ2d8W^M~~(Ec(&f%gCC7=%vysecU9PQGfi*J;ql#DNZRVCEn^?d5=Ws%|mHzLn?wFC!wG$PB z5Id>C+ndzdZ5TW^sFllFsg-*i-fpV@2U?gqb!7;~1@m%Wr>d=1j;vNXfc-U?Q>!h9 zAA^zm`h9Te7OSph-X~T6nJs8^xzzV_5X~a~ulm=j!ExzA?M^AlVv|P2H=yEO&Z|b~SZtuQ z-eWMU@QtdCsy2UCtp+aSLPW#c1*oui`p*!~Qf(gioOfi>W+Y)KQ^XqJw>Vwtpr+$t zNT$-EBohzAF^$y{p>S z+z=Ra3#PiuV5F|=8@PE?QPoy#lC9m)CU>>IeQ#1rVF z76EimckOo)tq-Nq?gG8C$e=%}mCa4wj_}4LST#7NgD3!87SrHvDgW5xuNOB<&;=mr z5;bsh4#o}@n7^vS+D!%F(PvP5ryz>mdn$gh+Ra(oB3TFUWRjr@0Gs7@Z7F|0(5FBR zcB|l8fQG#G`!-?;YZn?65K&G`r);DxK6xHDWQU88KpOF<9 zaFQArYVNrO~pE;a@B! zLpT_wrw96EsI69SewgQ-EtX%!buFJlV^Ke`Du;PfJ5!64_Ih>ShXU*_=*k&2#rGAm zDGN%@Q5LA@)@*89`*j;-!7BB-6)+N56d2qNl^C4fHc*fyjdJb!G<1ut(Yi~TAQe?9 zT8yU5_NxZcCjA}lg+u9@bTCtEEAQm97lH06YTz}VIjCmu6Th4bF;zP8<_T1Cuunzn z8+eWbdaCb*n)-q%2K}Y#o=iv(uR(t+V1soiVHIwm3eN@(HSixnbq!W>T0(Wzd(%X< zmM&*5f|bi7>EpHZ3MS~}a+-nU7UNUJgz+ifPRsgWCL}PSKg}8A5|^FvgE!j_Z+ZfFbu;47S89h>lmK3NJa`T}yq#U+*SB*#cneY3!oOJw z;H`clPJKt&;SEUuuPPqA3_HBE1n_u2R!n_aewMAi^N|!!Up71*2i|x)yb%fDO^*jJ z%MPz&0(eF7;H^2+R^K`##jEem>2c~i%?__T0lZo9;N{!lbxr_pNIZBV+g9JzNQzhA zw0Q7l*x^+rfH(iKIQ&!X@bVJC8xap)Q|r$mr-_jLlVHNiU%*l4lgYMyu5humY-p( z?|dZ1)0Yho$EojlJG>DI;7yMQFUt#M^Zd}+3-M|`i{558<7Csbim`9qMVxx zk7tVX@l2M4Hz`vM7GXTYk>?3a!QiM2!^+{Irp%UUYkmC8`>|reTZ8-fj7+tZ>$MksU6;|c<}OV@Xj#7bJ*bxi3d-dYOU`lOo`NYA>P76Zk>N= z@!-v{!Mo1{ZeMTaNtZ`YvEfL|ET zFxGbs-YoU)91q??mNwfz6TE45c&jVp)OVB(-cS>~d^^0Vcu!RVVTU&&9=xXGt@Zt$DUtdv$D5_T9pk~9ZG-o?4o@3@ zjJLuIrqx|d0<~>BNd4JBh?m*X5)yiL+ zye<{#-DId4*1KaZ75f|IqAgduUxsy*khL|eFU#kNPmcq2SScS|g$3`6u)tIZgeApD zA@K9DcHAbHOJs~Q3G~8RbhGy)xk!d!0>gS?2_VZtgp4yh%U5x=@NOM=0ahaWy8&3P zd8@%Ix#o>}{HFD8)wQ|&eUgf`({2^4hGEIO0alOSvfSACgwdR~>aDi=5~aLn>FdCO znCsqzy`BVSy5$C%@XGh1V+j_!T8K_t{N~cPxCZF1KpesFw`XFQsIP=iL}sq9OFo2t zK_E_8v$S9G4h#Qj^Crb<0Q~^OY>@Jp5>ePr5=jNHrdXEm-d7i5(V2gWw#%D~#{Qj9 zHn{+g=rM;U&;xN^p6p#}?Y=gMbHSD^NE|^m3-JRaD^>p{EIu#8d6H_Y?FGss%RJdT z3X}!AkVUCtkd+EGscM7V3mteX%0*C$ORZ;V6z_F{$F&m&lXKVU4v&AkCwsRRZ-CIF z8vI*~XoCJtYJCCMjj?bn%D+S8@&yi$YlGr@0ZH!cE&YQ>Wk%v5j+5o;85)349VnI` z>*KEF_7=1ZjHglEGXZmXA?B1C{-iPdQ2+0Qo45kk63@bA4Do zM)}M+dVl4!8YK^ad`54Dd~Srbix| zx7%O&Oh?HBAfIzLK|a~Am51dMO+Ftv_E$bTQSt!D=OF?4_+cv#%O{$A&TYHD@)?4X z2S7fJ-$Op3_J`#Y-M@Y)ZGYu6A0-cfdj$t zeCDI%0g%tYHIR=Rw(@ZMiPnA^ckQoyI_^Fo@|p7qgnU-O z${vo=QN*)0D415pral=*~Za>l5Ph)6*<Ax`%#udK1aeE61lJid4&}_;w@gQ=SRD&5c9UnoAJj33vcdDO&ma zAZWCkQ}L-1&tPXx;F6$_ckE0xcxSqPSlc@B47f(*_Y)lsT0-=& zyo&)R`Z<_{a|v?-1&A=ku~U4{RB??0UQ?ZX!{JCJIA;;%?!YN`9$wEd1|M-QpIGoy zYn&g2o0{s}{(9!ciMOB|5XB$S262qx`+vcL5bm_m%=x zsw=lV)f2eOACsmDb2S75%}zT3P5o&%~vIDQh4l9 zibpAeJ~WCd<|kixfP0uVDN>1hFG6Or<}~&RR@S^lfF_o-lU)bpf@GZO^`>(!5Q%SP zj}J)&hinIto`6rLdjbzQbiCavKGUc#yead(^%H(qX@y3dpnF> zgSLsow>RMs?(sZ;Td6eMeJO2R*tYS6cJA&*bo>W{c*?H9x}n+T*6>1+ba6EB$!f}5 zl$MBFF>byI3>R-4iNZIfD6f0C(_jLOLf#SV?5>mp>zh?n`(YRKy1q{tGJ;EON-yVT zieZ64rGY-UVsf!EbrC*rzX2L9@62%RRDAEryu%Tvj-v?u_{5V^TMmjYhkMI!oGux^nC}b z%fItv??gqj9Na@t!~fn5ZzMP#y9e|*Tk-PxNGE-ZGXuK_&~=1HptWm!Zv!I~T7y?@ ze~H{?gGQEH6B>>BidRwE{Z@F7K}h3V?MdHR|j98*M9kX^!W>vw(D~}G+fkv zbLjQ?R*+-V=MT{DY4(|&nOUD_EdY^k%8pU$^Vi)Dtv>&?{T{PEUk-w_R!{qU74(PuXa(&-cG()aUD|&u^iFJ{b0SHTd*D(C2+?4!%C04;=fi z&%H>YO`p3ggsPkd8*r%ojSoIG>+{*@uQh${VAJPg*@>JaJ9MefKj4JhL+L;N1)Bj) z{>B?~jrx2e^?55g=!2opX;QI_LxDr$Z=CbV!Pn=TR{tJ-z6GW2`aEntROK(Q0f$+{rqLsh20Rvv17J{#m1{pTS1YfYa!+VuGz5;;+J=u)3= zKmE|!=iFZ{`uvAijQTu+`ds|k!O`at6x*TK=LZ`OzCOSE(eKgc_fgue&#%vcs{9MK z@=)vZaFAov=eN*b$MT==qdvbXJ9MefryXK_cJH?6bN|^!eU|?7By`XR!{3-ru^oDS ze*1%iug`nm|2_Kr3rgGddE={4m9JMGdVLOp9HTyO#c);AXHL7C{f)u&H@-jyeZKBA z9)frX=b!gj_4)2uMtv@$ef|eJ=!2opbrjp7*XM2T9(;X16FByNJlK^K+Qx&OUWTf4 zfDJg*_W7+9W_>;x{k5jg?QHs7_Z)~kN_Oblc#wy{9!h;)XR*&OKWo(I%c;*_qJusd z`n(f-`XA`?Da#MOK3`t{d-VBAl(y@0zZan@g|Gn!K%b?#<=tfAtLNgZ4{{Caa&*z} zNaMDCSy-95z!^5qek>J_=lWo0b|SrGhc5NGFcbQG2-XGN)_IL@o-yk2mDJ;oA08Y% zKAYk?^mch@-ND!6N0$B`J$?+O?RtFA^H7ny(DwIRj|+$WR{i}Ox@1j%GeMKx7)SK? zqZdHjMA?B${e6kYOdkUMeQA%y4xj#%QGcZ!9`fG7(cfDsrbDm4k1sy>`n#(3_vr6` zQQEG*@BANBWD(5ZA=KZs=z?V^hbRM0n*JJtGa~x?FPJ^?rtGk#{!ZZ`(udHWaQ(}o z$Cv-jsK+usBZv<8VE7ZWD6T`V#~;3P@b$PIaO}T-d=x3P`NzAThN}FqwS~KA%l}-hvUeKHhWl(C7c9KHvX@QJ>}f@>}SL4~9Ok25bHY z`n>PWgRjr$1IPaBb1zb8)8{TvLRC(K4cKqH9M

e6ZX9Vc`(Be|7FEy2(G~?$Rz9 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%0AGe8@=UM{ZfUMB@YKg#B zkyOW@-rwt~(}DIViKwS#HLnqnQk>oUxV&fuxAGE>pM$AXimMZw0SC%rw1(?PN5JX}YAjCH*z{-*mMQl^Yq3!xoQXK^C&+Y)C4#H% zFp4w9DUS(FccKyHb%W+a^bv9zMT6UehypUPQjp0lwF0IfQgpTJKgAPsfKd9$ zB#i|~L(H%&N0AzXd3e^;Ap}$)=^$I18`53iG?DJ!tE3lR%77)lVlfceJhXiFNTQ3T zcOKIEZZB>c;j8*$dh}Cv2QhcVIyI;~6}_^A5bJcqqC2&roB^UMHS6h0-pbA1J7P&) z7i1)RNC8n)2xC;M7p86os|hmdqqOy5(gih?8rOL42tg8`VA)hM`>9YyPnWG%nF87w zp!`5Ph!k(Rb+^cdMeQnb3u#9w?gT+y`t@?IyI8ZS;ySOhJu^0aekQ zg43o0;)P{#hIV3FQ3$v|GB-of zjRb(Z_6oJse+5Q4DOk(YAggb&o{?TwuhKpxe8r{(O;1|DNY$pALN%(I_Ic)?V9&f> zhPfCfMxqxQfThC*oY~ zq`fvWg>K!=mx|YZQJ_XODZ{PjD~}D81J{!9b~_X`GRhi35u+VrgOHRpax0Cbv$}`C zX?rAeTh+wcNuEsF=GNO)s7ER*gcgFWd<9B`Q|NgGgVG!FY15>GGDzyff87iPlNZS-&nQm zprl)3JIq+MT&c7tEe+BIr}Fe@MMw>ZE)P{`f;VKQ@^j1hRZmdZXqts;<}uJ-H`VBX zQxw!tYFy*~It??j?`W8c7)yO=$YX`rvQS)4HySr+El_3`T{?>Mt=0^>X6$RK(}%K& zsU}+~?hhvX?eHvF8!WemQKVFCUu$Ku9Tg{9p0HG-;ulxDd9s!ChxE$_^1P zGGP(!J4jhrpbd~659_0$jr z5|*SEYKI9$P)3+B=AuDu7{cqfrG?R)4Yu zy-U=mMP&=-2`aWZN+4&Wfj|rjTlG!K{Kx+4WL#ycm@uZO2?!%X*i4oFm5>8d_Jk3% zOxIE)63aE4Zxjt>NdMB-( zv|3B+WInw~6j<7P8r@g9=(A3G=D?%T1KZ9MlW%LsauVyIbO`Sn_{`zt-$8!+$tZ|p zrbOqnXa1J-t9?DwFL(KWsCyImDvRs?KM}5^D&C+{qqQ}yv4R_+ijrb95s)VuYU)

hW-k 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 N|F>UB$`2R*{{WeR(dGaE diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/echocancelling.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/echocancelling.py similarity index 84% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/echocancelling.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/echocancelling.py index 3d45361b848b..f6f3e55d8faa 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/echocancelling.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/echocancelling.py @@ -2,12 +2,12 @@ def main() { var fs = 8000; # var step = 1/8000; # print(step); - var t = getRangeOfVector(0,100, 0.000125); + var input = getRangeOfVector(0, 100000000, 0.000125); var f_sig = 500; var pi = 3.14159265359; var getMultiplier = 2 * pi * f_sig; # print(getMultiplier); - var getSinDuration = gain(t, getMultiplier); + var getSinDuration = gain(input, getMultiplier); # print(getSinDuration); var clean_sig = sin(getSinDuration ); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/hearing_aid.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/hearingAid.py similarity index 93% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/hearing_aid.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/hearingAid.py index 744c023a45f0..8f849c842233 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/hearing_aid.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/hearingAid.py @@ -2,7 +2,7 @@ def main() { var fs = 8000; # var step = 1/8000; # print(step); - var input = getRangeOfVector(0, 20000000, 0.000125); + var input = getRangeOfVector(0, 100000000, 0.000125); var f_sig = 500; var pi = 3.14159265359; var getMultiplier = 2 * pi * f_sig; diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFIRFilterDesign.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFIRFilterDesign.py index 3be61f2b89b7..267aaea41edc 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFIRFilterDesign.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFIRFilterDesign.py @@ -8,7 +8,7 @@ def main() { # var a10 = getRangeOfVector(0, 400, 0.000125); # var orig = sin(a10); - var N = 20000001 ; + var N = 11 ; # for cut-off freq var pi = 3.14159265359; diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFull.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFull.py index c0b8d7851621..8dc27840a8ca 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFull.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFull.py @@ -12,7 +12,7 @@ def main() { # var step = 1/8000; # print(step); var duration = 0.05 ; # 50 milli-secs - var input = getRangeOfVector(0, 30000000, 0.000125); + var input = getRangeOfVector(0, 100000000, 0.000125); # print(c); # var c = getRangeOfVector(0,10, 0.000125); var f_sig = 500; diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/noisecancelling.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/noisecancelling.py index ed37be3a42d8..2b05325f4c92 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/noisecancelling.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/noisecancelling.py @@ -2,7 +2,7 @@ def main() { var fs = 8000; # var step = 1/8000; # print(step); - var input = getRangeOfVector(0, 20000000, 0.000125); + var input = getRangeOfVector(0, 100000000, 0.000125); var f_sig = 500; var pi = 3.14159265359; var getMultiplier = 2 * pi * f_sig; diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/periodogram2Conv1.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/periodogram2Conv1.py index 9ee480e6c033..e84f0c82b509 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/periodogram2Conv1.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/periodogram2Conv1.py @@ -14,7 +14,7 @@ def main() { #size 10 # var a10 = [ 10,20,30,40,50,60,70,80,90,100]; - var input = getRangeOfVector(0, 10, 1); + var input = getRangeOfVector(0, 20000, 1); # var input = [1,2,3,4]; # print(a10); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/vibrationAnalysis.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/vibrationAnalysis.py similarity index 78% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/vibrationAnalysis.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/vibrationAnalysis.py index 1fb6defd7e3d..50a59c502802 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/vibrationAnalysis.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/vibrationAnalysis.py @@ -2,20 +2,20 @@ def main() { var fs = 1000; # var step = 1/fs; # print(step); - var t = getRangeOfVector(0,fs,0.001); + var input = getRangeOfVector(0, 100000, 1); var pi = 3.14159265359; var getMultiplier = 2 * pi * 50; # print(getMultiplier); - var getSinDuration = gain(t, getMultiplier); + var getSinDuration = gain(input, getMultiplier); var sig1 = sin(getSinDuration ); var getMultiplier2 = 2 * pi * 120; - var getSinDuration2 = gain(t, getMultiplier2); + var getSinDuration2 = gain(input, getMultiplier2); var sinsig2 = sin(getSinDuration2); var sig2 = gain(sinsig2, 0.5); var signal = sig1 + sig2; var noise = delay(signal, 5); var noisy_sig = signal + noise; - var threshold = 4; + var threshold = 0.2; var fft_real = fft1dreal(noisy_sig); var fft_img = fft1dimg(noisy_sig); @@ -24,7 +24,7 @@ def main() { # sum = sum(sq_abs) var sum1 = sum(sq_abs); # res = gain(sum , 1/N) - var len1 = len(t); + var len1 = len(input); var res = sum1 / len1; # print(sq_abs); var GetThresholdReal = threshold( sq_abs , threshold); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/voiceActivityDetection.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/voiceActivityDetection.py similarity index 76% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/voiceActivityDetection.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/voiceActivityDetection.py index d1a5efb4fc12..3a6d44065eb7 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/voiceActivityDetection.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/voiceActivityDetection.py @@ -2,16 +2,16 @@ def main() { var fs = 1000; # var step = 1/fs; # print(step); - var t = getRangeOfVector(0,2000,0.001); + var input = getRangeOfVector(0, 100000000, 1); var pi = 3.14159265359; var getMultiplier = 2 * pi * 5; # print(getMultiplier); - var getSinDuration = gain(t, getMultiplier); + var getSinDuration = gain(input, getMultiplier); var signal = sin(getSinDuration ); var noise = delay(signal, 5); var noisy_sig = signal + noise; - var threshold = 1.8; + var threshold = 0.8; var GetThresholdReal = threshold( noisy_sig , threshold); var zcr = zeroCrossCount(GetThresholdReal); print(GetThresholdReal); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/underWaterCommunication.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/underWaterCommunication.py deleted file mode 100644 index e3290a755d24..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/underWaterCommunication.py +++ /dev/null @@ -1,32 +0,0 @@ -def main() { - var fs = 1000; - # var step = 1/fs; - # print(step); - var t = getRangeOfVector(0,1000,0.001); - var pi = 3.14159265359; - var getMultiplier = 2 * pi * 5; - # print(getMultiplier); - var getSinDuration = gain(t, getMultiplier); - var signal = sin(getSinDuration ); - - var noise = delay(signal, 5); - var noisy_sig = signal + noise; - - - #design a low-pass filter : filterOrder = 5(odd) , cut-off freq=10 - # get wc = 2 * pi * cutoff_freq / fs - # get the filter response using filter(b,a, noisy_sig) - var fc = 1000; - # var Fs = 8000; - var wc = 2 * pi * 1000 / 500; #wc should vary from 0 to pi - var N = 5; - # var hid = sinc(wc, N); - var lpf = lowPassFIRFilter(wc, 1); #ideal low -pass filter - var lpf_w = lpf * hamming(N); - var FIRfilterResponse = FIRFilterResponse(noisy_sig, lpf_w); - - var threshold = 0.5; - var GetThresholdReal = thresholdUp(FIRfilterResponse, threshold, 0); - print(GetThresholdReal); - -} \ No newline at end of file From 0f6eb1012df9508edaeecca3e9ab88f50373e4e7 Mon Sep 17 00:00:00 2001 From: "Kuo, Mei-Chun" <94007620+Megan0704-1@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:09:29 -0700 Subject: [PATCH 11/45] Qam modulate (#15) * qam modulation impl * reverse modification for vardecl --- .../Output/TryDSPApps/qam_modulate.py | 12 ++ .../dsp/SimpleBlocks/include/toy/Lexer.h | 3 + .../dsp/SimpleBlocks/include/toy/Ops.td | 46 ++++++++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 83 ++++++++++++++ .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 108 +++++++++++++++++- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 41 +++++-- mlir/test/Examples/Toy/Ch2/scalar.toy | 4 + 7 files changed, 286 insertions(+), 11 deletions(-) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/qam_modulate.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/qam_modulate.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/qam_modulate.py new file mode 100644 index 000000000000..87ec2cfbab9e --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/qam_modulate.py @@ -0,0 +1,12 @@ +def main() { + var input_data = [0,1,1,0,1,1,1,0]; + # print(input_data); + var real = qam_modulate_real(input_data); + var imagine = qam_modulate_imagine(input_data); + print(real); + print(imagine); + # var real_part = [1, 1, 1, 1]; + # var img_part = [1, -1, 1, -1]; + # var decoded_data = qam_demodulate(real_part, img_part); + # print(decoded_data); +} diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Lexer.h b/mlir/examples/dsp/SimpleBlocks/include/toy/Lexer.h index 94469b29b103..38678e23f553 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Lexer.h +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Lexer.h @@ -36,6 +36,7 @@ enum Token : int { tok_bracket_close = '}', tok_sbracket_open = '[', tok_sbracket_close = ']', + tok_comma = ',', tok_eof = -1, @@ -145,6 +146,8 @@ class Lexer { return tok_def; if (identifierStr == "var") return tok_var; + if(identifierStr == ",") + return tok_comma; return tok_identifier; } diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 5a5ffdcb0fe7..1007b659a902 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -2122,6 +2122,52 @@ def GenerateDTMFOp : Dsp_Op<"generateDtmf", let hasVerifier = 1; } +//===----------------------------------------------------------------------===// +// QamModulateRealOp real +//===----------------------------------------------------------------------===// + +def QamModulateRealOp : Dsp_Op<"qam_modulate_real", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Dsp dialect qam modulation real operation"; + let description = [{ + Performs a digital modulation on input tensor. + }]; + + let arguments = (ins F64Tensor:$signal); + + let results = (outs F64Tensor:$real); + + + let builders = [ + OpBuilder<(ins "Value":$signal)> + ]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// QamModulateImgOp imagine +//===----------------------------------------------------------------------===// + +def QamModulateImgOp : Dsp_Op<"qam_modulate_imagine", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Dsp dialect qam modulation imagine operation"; + let description = [{ + Performs a digital modulation on input tensor. + }]; + + let arguments = (ins F64Tensor:$signal); + + let results = (outs F64Tensor:$imagine); + + + let builders = [ + OpBuilder<(ins "Value":$signal)> + ]; + + let hasVerifier = 1; +} + #endif // TOY_OPS diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index 5b45f98c85d5..e33c9371f06d 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -2757,6 +2757,89 @@ auto finalShape = freq * duration; getResult().setType(outputType); } +//===----------------------------------------------------------------------===// +// QamModulateRealOp +//===----------------------------------------------------------------------===// + +void QamModulateRealOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value signal) { + auto tensorType = UnrankedTensorType::get(builder.getF64Type()); + state.addTypes({tensorType}); + + state.addOperands({signal}); +} +void QamModulateRealOp::inferShapes() { + auto signalType = llvm::dyn_cast(getSignal().getType()); + auto signalShape = signalType.getShape(); + + SmallVector outputShape(signalShape); + for(size_t i=0; i(getSignal().getType()); + + if(!signalType) { + llvm::errs() << "expect a ranked tensor for signal input, get " << getSignal(); + return mlir::failure(); + } + + auto signalRank = signalType.getRank(); + + if(signalRank != 1 ) { + llvm::errs() << "expect 1 dimensional signal, get " << signalRank; + return mlir::failure(); + } + + return mlir::success(); +} + +//===----------------------------------------------------------------------===// +// QamModulateImgOp +//===----------------------------------------------------------------------===// + +void QamModulateImgOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value signal) { + auto tensorType = UnrankedTensorType::get(builder.getF64Type()); + state.addTypes({tensorType}); + + state.addOperands({signal}); +} +void QamModulateImgOp::inferShapes() { + auto signalType = llvm::dyn_cast(getSignal().getType()); + auto signalShape = signalType.getShape(); + + SmallVector outputShape(signalShape); + for(size_t i=0; i(getSignal().getType()); + + if(!signalType) { + llvm::errs() << "expect a ranked tensor for signal input, get " << getSignal(); + return mlir::failure(); + } + + auto signalRank = signalType.getRank(); + + if(signalRank != 1 ) { + llvm::errs() << "expect 1 dimensional signal, get " << signalRank; + return mlir::failure(); + } + + return mlir::success(); +} //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 7ffc17f8566e..1c05402b17e1 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -7639,6 +7639,112 @@ struct GenerateDTMFOpLowering : public ConversionPattern { } }; +struct QamModulateRealOpLowering : public ConversionPattern { + QamModulateRealOpLowering(MLIRContext *ctx) : + ConversionPattern(dsp::QamModulateRealOp::getOperationName(), 1, ctx) {} + + LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); + + QamModulateRealOpAdaptor adaptor(operands); + Value signal = adaptor.getSignal(); + + + llvm::ArrayRef outputShape = output.getShape(); + + // constant vals; + Value negOneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value oneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + + AffineExpr d0; // declare one dimension affine expr + bindDims(rewriter.getContext(), d0); // bind affine expr d0 to current input (real array) dimension + + // real affine map + AffineMap signalMap = AffineMap::get(1, 0, ArrayRef{d0*2}, rewriter.getContext()); + // output affine map + AffineMap outputRealMap = AffineMap::get(1, 0, ArrayRef{d0}, rewriter.getContext()); + + // loops + int64_t lb=0, step=1, ub=outputShape[0]; + /* looping i*/ + AffineForOp forOpI = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOpI.getBody()); + auto ivI = forOpI.getInductionVar(); + + + // input bound check + Value signalNum = rewriter.create(loc, signal, signalMap, ValueRange{ivI}); + + Value zeroReal = rewriter.create(loc, arith::CmpFPredicate::OEQ, signalNum, zeroVal); + + Value out = rewriter.create(loc, zeroReal, negOneVal, oneVal); + + rewriter.create(loc, out, alloc, outputRealMap, ValueRange{ivI}); + rewriter.replaceOp(op, alloc); + + return success(); + } +}; + +struct QamModulateImgOpLowering : public ConversionPattern { + QamModulateImgOpLowering(MLIRContext *ctx) : + ConversionPattern(dsp::QamModulateImgOp::getOperationName(), 1, ctx) {} + + LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); + + QamModulateRealOpAdaptor adaptor(operands); + Value signal = adaptor.getSignal(); + + + llvm::ArrayRef outputShape = output.getShape(); + + // constant vals; + Value negOneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value oneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + + AffineExpr d0; // declare one dimension affine expr + bindDims(rewriter.getContext(), d0); // bind affine expr d0 to current input (real array) dimension + + // real affine map + AffineMap signalMap = AffineMap::get(1, 0, ArrayRef{d0*2+1}, rewriter.getContext()); + // output affine map + AffineMap outputImgMap = AffineMap::get(1, 0, ArrayRef{d0}, rewriter.getContext()); + + // loops + int64_t lb=0, step=1, ub=outputShape[0]; + /* looping i*/ + AffineForOp forOpI = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOpI.getBody()); + auto ivI = forOpI.getInductionVar(); + + + // input bound check + Value signalNum = rewriter.create(loc, signal, signalMap, ValueRange{ivI}); + + Value zeroReal = rewriter.create(loc, arith::CmpFPredicate::OEQ, signalNum, zeroVal); + + Value out = rewriter.create(loc, zeroReal, negOneVal, oneVal); + + rewriter.create(loc, out, alloc, outputImgMap, ValueRange{ivI}); + + rewriter.replaceOp(op, alloc); + + return success(); + + } +}; + } // namespace //===----------------------------------------------------------------------===// @@ -7711,7 +7817,7 @@ void ToyToAffineLoweringPass::runOnOperation() { FIRFilterYSymmOptimizedOpLowering, FFT1DRealSymmOpLowering, FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering, ShiftRightOpLowering, MatmulOpLowering, - ThresholdUpOpLowering>(&getContext()); + ThresholdUpOpLowering, QamModulateRealOpLowering, QamModulateImgOpLowering>(&getContext()); // With the target and rewrite patterns defined, we can now attempt the // conversion. The conversion will signal failure if any of our `illegal` diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index 8efe06b2fd35..5bd337504199 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -858,6 +858,26 @@ class MLIRGenImpl { operands[2]); } + // qam modulate op + if(callee == "qam_modulate_real") { + if(call.getArgs().size() != 1) { + emitError(location, "MLIR codegen encountered an error: dsp.QamModulateRealOp " + "accepts 1 arguments"); + return nullptr; + } + + return builder.create(location, operands[0]); + } + + if(callee == "qam_modulate_imagine"){ + if(call.getArgs().size() != 1) { + emitError(location, "MLIR codegen encountered an error: dsp.QamModualteImgOp " + "accepts 1 arguments"); + return nullptr; + } + + return builder.create(location, operands[0]); + } // Builtin calls have their custom operation, meaning this is a // straightforward emission. // if(callee == "delay"){ @@ -924,21 +944,22 @@ class MLIRGenImpl { return nullptr; } - mlir::Value value = mlirGen(*init); + + mlir::Value value; + // Register the value in the symbol table. + value = mlirGen(*init); if (!value) - return nullptr; + return nullptr; - // We have the initializer value, but in case the variable was declared - // with specific shape, we emit a "reshape" operation. It will get - // optimized out later as needed. + // We have the initializer value, but in case the variable was declared + // with specific shape, we emit a "reshape" operation. It will get + // optimized out later as needed. if (!vardecl.getType().shape.empty()) { - value = builder.create(loc(vardecl.loc()), - getType(vardecl.getType()), value); + value = builder.create(loc(vardecl.loc()), + getType(vardecl.getType()), value); } - - // Register the value in the symbol table. if (failed(declare(vardecl.getName(), value))) - return nullptr; + return nullptr; return value; } diff --git a/mlir/test/Examples/Toy/Ch2/scalar.toy b/mlir/test/Examples/Toy/Ch2/scalar.toy index b109898fb6d3..a4f4211a5faf 100644 --- a/mlir/test/Examples/Toy/Ch2/scalar.toy +++ b/mlir/test/Examples/Toy/Ch2/scalar.toy @@ -2,6 +2,10 @@ def main() { var a<2, 2> = 5.5; + var b = 6; + var c = 8; + var d = b+c; + print(d); print(a); } From daf7f1002815463e7c08e190d05a40cc182115e5 Mon Sep 17 00:00:00 2001 From: "Kuo, Mei-Chun" <94007620+Megan0704-1@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:27:13 -0700 Subject: [PATCH 12/45] implement qam demodulate (#16) Co-authored-by: AtharvaKhedkar --- .../Output/TryDSPApps/qam_demodulate.py | 10 +++ .../dsp/SimpleBlocks/include/toy/Ops.td | 23 ++++++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 45 ++++++++++++ .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 72 ++++++++++++++++++- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 9 +++ 5 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/qam_demodulate.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/qam_demodulate.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/qam_demodulate.py new file mode 100644 index 000000000000..6de430689119 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/qam_demodulate.py @@ -0,0 +1,10 @@ +def main() { + # var input_data = [1,1,1,0,1,1,1,0]; + # print(input_data); + # var modulated_symbols = qam_modulate(input_data); + # print(modulated_symbols); + var real_part = [1, 1, 1, 1]; + var img_part = [1, -1, 1, -1]; + var decoded_data = qam_demodulate(real_part, img_part); + print(decoded_data); +} diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 1007b659a902..01bfcdc32e1e 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -2169,6 +2169,29 @@ def QamModulateImgOp : Dsp_Op<"qam_modulate_imagine", } +//===----------------------------------------------------------------------===// +// QamDemodulateOp +//===----------------------------------------------------------------------===// + +def QamDemodulateOp : Dsp_Op<"qam_demodulate", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Dsp dialect qam demodulation operation"; + let description = [{ + Takes in 2 arrays, one is the real part of a signal the other is the imaginary part of a signal. + Returns the decoded binary output. + }]; + + let arguments = (ins F64Tensor:$real, F64Tensor:$imagine); + + let results = (outs F64Tensor:$output); + + let builders = [ + OpBuilder<(ins "Value":$real, "Value":$imagine)> + ]; + + + let hasVerifier = 1; +} #endif // TOY_OPS diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index e33c9371f06d..f85b9620cd10 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -2757,6 +2757,51 @@ auto finalShape = freq * duration; getResult().setType(outputType); } +//===----------------------------------------------------------------------===// +// QamDemodulateOp +//===----------------------------------------------------------------------===// + +void QamDemodulateOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value real, mlir::Value imagine) { + state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); + state.addOperands({real, imagine}); +} + +void QamDemodulateOp::inferShapes() { + auto realType = llvm::dyn_cast(getReal().getType()); + auto realShape = realType.getShape(); + SmallVector outputShape(realShape); + + for(size_t i=0; i(getReal().getType()); + auto imagineType = llvm::dyn_cast(getImagine().getType()); + + if(!realType) { + llvm::errs() << "expect a ranked tensor for real part array, get " << getReal() << " instead\n"; + return mlir::failure(); + } + if(!imagineType) { + llvm::errs() << "expect a ranked tensor for imagine part array, get " << getImagine() << " instead\n"; + return mlir::failure(); + } + + auto realShape = realType.getShape(); + auto imagineShape = imagineType.getShape(); + + if(realShape.size() != imagineShape.size()) { + llvm::errs() << "expect real array and imagine array to have same tensor shape.\n"; + return mlir::failure(); + } + + return mlir::success(); +} + //===----------------------------------------------------------------------===// // QamModulateRealOp //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 1c05402b17e1..8c3e686659ca 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -7744,6 +7744,76 @@ struct QamModulateImgOpLowering : public ConversionPattern { } }; +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: QAM demodulate operations +//===----------------------------------------------------------------------===// + +struct QamDemodulateOpLowering : public ConversionPattern { + QamDemodulateOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::QamDemodulateOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + + auto loc = op->getLoc(); + // output mem alloc and dealloc + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); + + QamDemodulateOpAdaptor qamDemodualteAdaptor(operands); + Value realVal = qamDemodualteAdaptor.getReal(); + Value imgVal = qamDemodualteAdaptor.getImagine(); + + // ranked tensor type + auto realType = llvm::dyn_cast(op->getOperand(0).getType()); + + llvm::ArrayRef realShape = realType.getShape(); + llvm::ArrayRef outputShape = output.getShape(); + + // constant vals; + Value negOneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value oneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + + AffineExpr d0; // declare one dimension affine expr + bindDims(rewriter.getContext(), d0); // bind affine expr d0 to current input (real array) dimension + + // real affine map + AffineMap realMap = AffineMap::get(1, 0, ArrayRef{d0}, rewriter.getContext()); + // imagine affine map + AffineMap imgMap = AffineMap::get(1, 0, ArrayRef{d0}, rewriter.getContext()); + // output affine map + AffineMap outputMapReal = AffineMap::get(1, 0, ArrayRef{d0*2}, rewriter.getContext()); + AffineMap outputMapImagine = AffineMap::get(1, 0, ArrayRef{d0*2+1}, rewriter.getContext()); + + // loops + int64_t lb=0, step=1, ub=realShape[0]; + /* looping i*/ + AffineForOp forOpI = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOpI.getBody()); + auto ivI = forOpI.getInductionVar(); + + + // input bound check + Value realNum = rewriter.create(loc, realVal, realMap, ValueRange{ivI}); + Value imgNum = rewriter.create(loc, imgVal, imgMap, ValueRange{ivI}); + + Value negReal = rewriter.create(loc, arith::CmpFPredicate::OEQ, realNum, negOneVal); + Value negImagine = rewriter.create(loc, arith::CmpFPredicate::OEQ, imgNum, negOneVal); + + Value out1 = rewriter.create(loc, negReal, zeroVal, oneVal); + Value out2 = rewriter.create(loc, negImagine, zeroVal, oneVal); + + rewriter.create(loc, out1, alloc, outputMapReal, ValueRange{ivI}); + rewriter.create(loc, out2, alloc, outputMapImagine, ValueRange{ivI}); + + rewriter.replaceOp(op, alloc); + + return success(); + } +}; // qam_demodulate op } // namespace @@ -7817,7 +7887,7 @@ void ToyToAffineLoweringPass::runOnOperation() { FIRFilterYSymmOptimizedOpLowering, FFT1DRealSymmOpLowering, FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering, ShiftRightOpLowering, MatmulOpLowering, - ThresholdUpOpLowering, QamModulateRealOpLowering, QamModulateImgOpLowering>(&getContext()); + ThresholdUpOpLowering, QamModulateRealOpLowering, QamModulateImgOpLowering,QamDemodulateOpLowering>(&getContext()); // With the target and rewrite patterns defined, we can now attempt the // conversion. The conversion will signal failure if any of our `illegal` diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index 5bd337504199..a1e954ce9bf7 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -877,6 +877,15 @@ class MLIRGenImpl { } return builder.create(location, operands[0]); + } + // qam_demodulate + if(callee == "qam_demodulate") { + if(call.getArgs().size() != 2) { + emitError(location, "MLIR codegen encountered an error: dsp.QamDemodulateOp" + "accepts 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); } // Builtin calls have their custom operation, meaning this is a // straightforward emission. From 6d0b6a39c7c3500707896c0cdfa69669628b61c6 Mon Sep 17 00:00:00 2001 From: Atharva Khedkar <55466743+AtharvaKhedkar@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:37:33 -0700 Subject: [PATCH 13/45] Added find_peaks (#17) Co-authored-by: hwisooso --- .../Output/TryDSPApps/find_peaks.py | 8 + .../TryDSPApps/target_identification.py | 35 +++ .../dsp/SimpleBlocks/include/toy/Ops.td | 48 ++++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 56 +++-- .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 209 +++++++++++++++++- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 10 + 6 files changed, 348 insertions(+), 18 deletions(-) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/find_peaks.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/target_identification.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/find_peaks.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/find_peaks.py new file mode 100644 index 000000000000..4290babc8e72 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/find_peaks.py @@ -0,0 +1,8 @@ +def main() { + + var signal = [0.4, 0.3, 0.6, 1.8, 0.9, 0.5, 0.2, 0.7, 1.2, 0.8, 2.0, 1.9, 1.8, 1.7, 1.8, 1.7]; + var peaks = find_peaks(signal, 0.5, 1); + + print(peaks); + +} diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/target_identification.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/target_identification.py new file mode 100644 index 000000000000..ae1d280d624b --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/target_identification.py @@ -0,0 +1,35 @@ +def main() { + + + var fs = 8000; + # var step = 1/8000; + # print(step); + var t = getRangeOfVector(0,100, 0.000125); + var f_sig = 500; + var pi = 3.14159265359; + var getMultiplier = 2 * pi * f_sig; + # print(getMultiplier); + var getSinDuration = gain(t, getMultiplier); + # print(getSinDuration); + var clean_sig = sin(getSinDuration ); + + #define a noise signal with freq = 3000 + var f_noise = 3000; + var getNoiseSinDuration = gain(t, 2 * pi * f_noise); + var noise = sin(getNoiseSinDuration); + var noise1 = gain(noise, 0.5); + + var noisy_sig = clean_sig + noise1; + # print(noisy_sig); + # print(clean_sig); + var mu = 0.01; + var filterSize = 32; + var y = lmsFilterResponse(noisy_sig, clean_sig, mu, filterSize); + print(y); + + #var signal = [0.4, 0.3, 0.6, 1.8, 0.9, 0.5, 0.2, 0.7, 1.2, 0.8, 2.0, 1.9, 1.8, 1.7, 1.8, 1.7]; + var peaks = find_peaks(y, 1.0, 20); + + print(peaks); + +} diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 01bfcdc32e1e..b25ec770150b 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -2192,6 +2192,54 @@ def QamDemodulateOp : Dsp_Op<"qam_demodulate", let hasVerifier = 1; } + +//===----------------------------------------------------------------------===// +// FindPeaksOp +//===----------------------------------------------------------------------===// + +def FindPeaksOp : Dsp_Op<"find_peaks", [Pure , DeclareOpInterfaceMethods]> { + let summary = "Find peaks from the signal. Since the number of peaks vary, the output is initialized as -1."; + let description = [{ + Input: signal, height, distance + Output: indices of peaks. All of none-used values are initialized as -1, so the length can be measured by this. + + Functionality: check the below original python-level code. + + def manual_find_peaks(signal, height, distance): + peaks = [] + for i in range(1, len(signal) - 1): + # Check if the current point is higher than its neighbors + if signal[i] > signal[i-1] and signal[i] > signal[i+1]: + # Check if it meets the height criterion + if signal[i] >= height: + # Check if it's far enough from the previously detected peak + if not peaks or i - peaks[-1] >= distance: + peaks.append(i) + return np.array(peaks) + + }]; + + let arguments = (ins F64Tensor:$signal, F64Tensor:$height, F64Tensor:$distance); + let results = (outs F64Tensor); + + // Indicate that the operation has a custom parser and printer method. + // let hasCustomAssemblyFormat = 1; + // let assemblyFormat = [{ + // `(` $input `:` type($input1 , $input2) `)` attr-dict `to` type(results) + // }]; + // Allow building a MulOp with from the two input operands. + + let builders = [ + OpBuilder<(ins "Value":$signal, "Value":$height, "Value":$distance)> + ]; + + // Indicate that the operation has a custom parser and printer method. + // let hasCustomAssemblyFormat = 1; + + // let hasVerifier = 1; + } + + #endif // TOY_OPS diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index f85b9620cd10..18a51871c3ec 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -629,21 +629,21 @@ void FFTImagOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, void FFTImagOp::inferShapes() { getResult().setType(getLhs().getType()); } //===----------------------------------------------------------------------===// -// MatmulOp -//===----------------------------------------------------------------------===// + // MatmulOp + //===----------------------------------------------------------------------===// -void MatmulOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, - mlir::Value lhs, mlir::Value rhs) { - state.addTypes(UnrankedTensorType::get(builder.getF64Type())); - state.addOperands({lhs, rhs}); -} + void MatmulOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs, mlir::Value rhs) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({lhs, rhs}); + } -// mlir::ParseResult MatmulOp::parse(mlir::OpAsmParser &parser, -// mlir::OperationState &result) { -// return parseBinaryOp(parser, result); -// } + // mlir::ParseResult MatmulOp::parse(mlir::OpAsmParser &parser, + // mlir::OperationState &result) { + // return parseBinaryOp(parser, result); + // } -// void MatmulOp::print(mlir::OpAsmPrinter &p) { printBinaryOp(p, *this); } + // void MatmulOp::print(mlir::OpAsmPrinter &p) { printBinaryOp(p, *this); } mlir::LogicalResult MatmulOp::verify() { @@ -655,7 +655,7 @@ mlir::LogicalResult MatmulOp::verify() { auto tensorRhs = getRhs().getType(); auto shapeOfRhs = tensorRhs.getShape(); - + if (shapeOfLhs[1] != shapeOfRhs[0]) return emitOpError("Matmul: the second dimension of LHS should be equal to the first dimention of RHS."); return mlir::success(); @@ -663,8 +663,8 @@ mlir::LogicalResult MatmulOp::verify() { /// Infer the output shape of the MatmulOp, this is required by the shape /// inference interface. -void MatmulOp::inferShapes() { - + void MatmulOp::inferShapes() { + // get the shape of Lhs & rhs // add the shape for each dimension // auto tensorInput = llvm::cast(getLhs().getType()); @@ -673,12 +673,12 @@ void MatmulOp::inferShapes() { auto tensorRhs = getRhs().getType(); auto shapeOfRhs = tensorRhs.getShape(); - + std::vector shapeForOutput; shapeForOutput.push_back(shapeOfLhs[0]); shapeForOutput.push_back(shapeOfRhs[1]); - + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( shapeForOutput, getLhs().getType().getElementType()); @@ -686,6 +686,28 @@ void MatmulOp::inferShapes() { getResult().setType(manipulatedType); } + +//===----------------------------------------------------------------------===// + // FindPeaksOp + //===----------------------------------------------------------------------===// + + void FindPeaksOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value signal, mlir::Value height, mlir::Value distance) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({signal, height, distance}); + } + + + /// Infer the output shape of the FindPeaksOp, this is required by the shape inference + /// interface. + void FindPeaksOp::inferShapes() { + getResult().setType(getSignal().getType()); + +} + + + + //===----------------------------------------------------------------------===// // zeroCrossCountOp //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 8c3e686659ca..721a58e8fdef 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -7126,6 +7126,213 @@ LoweredOp); } }; + + + +//===----------------------------------------------------------------------===// +// ToyToAffine AdditionalPatterns: Find peaks operations +//===----------------------------------------------------------------------===// + +//template + +struct FindPeaksOpLowering : public ConversionPattern { + FindPeaksOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::FindPeaksOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + + //Get the location of GainOp + auto loc = op->getLoc(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + + // allocation & deallocation for the result of this operation + auto memRefType = convertTensorToMemRef(tensorType); + auto alloc_output = insertAllocAndDealloc(memRefType, loc, rewriter); + + auto countMemRefType = MemRefType::get({}, rewriter.getIndexType()); + auto alloc_peaks_count = insertAllocAndDealloc(countMemRefType, loc, rewriter); + + typename dsp::FindPeaksOp::Adaptor findPeaksOpAdaptor(operands); + + + + + Value constant_minus_one = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + + Value constant_index_zero = rewriter.create(loc, rewriter.getIndexType(), rewriter.getIndexAttr(0)); + Value constant_index_one = rewriter.create(loc, rewriter.getIndexType(), rewriter.getIndexAttr(1)); + + rewriter.create(loc, constant_index_zero, alloc_peaks_count, ValueRange{}); + + + + auto signalType = llvm::dyn_cast(op->getOperand(0).getType()); + int64_t lb = 1 ; + int64_t ub = signalType.getShape()[0]-1; + int64_t step = 1; + + Value constant_len_singal = rewriter.create(loc, rewriter.getIndexType(), rewriter.getIndexAttr(signalType.getShape()[0])); + + //%distance = affine.load %alloc_distance[] : memref + auto distance_fp = rewriter.create( + loc, findPeaksOpAdaptor.getDistance(), ValueRange{}); + //f64 to index + Value distance_ui = + rewriter.create(loc, rewriter.getIntegerType(32), distance_fp); + Value distance = rewriter.create( + loc, rewriter.getIndexType(), distance_ui); + + + affine::AffineForOp forOpInit = rewriter.create(loc, lb, ub, step); + auto init_iter = forOpInit.getInductionVar(); + rewriter.setInsertionPointToStart(forOpInit.getBody()); + + rewriter.create(loc, constant_minus_one, alloc_output, ValueRange{init_iter}); + + rewriter.setInsertionPointAfter(forOpInit); + + + + affine::AffineForOp forOpSignal = rewriter.create(loc, lb, ub, step); + auto current_index = forOpSignal.getInductionVar(); + rewriter.setInsertionPointToStart(forOpSignal.getBody()); + + // %prev_index = arith.subi %current_index, %cst_one_index : index + // %signal_prev = memref.load %alloc_signal[%prev_index] : memref<10xf64> + // %signal_current = affine.load %alloc_signal[%current_index] : memref<10xf64> + // %signal_next = affine.load %alloc_signal[%current_index+1] : memref<10xf64> Q. How can I do this? + // %height = affine.load %alloc_height[] : memref + + AffineExpr ExprForPrev = + rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(1); + AffineMap addMapForPrev = AffineMap::get(1, 0, ExprForPrev); + + AffineExpr ExprForNext = + rewriter.getAffineDimExpr(0) + rewriter.getAffineConstantExpr(1); + AffineMap addMapForNext = AffineMap::get(1, 0, ExprForNext); + + auto signal_prev = rewriter.create( + loc, findPeaksOpAdaptor.getSignal(), addMapForPrev, ValueRange{current_index}); + auto signal_current = rewriter.create( + loc, findPeaksOpAdaptor.getSignal(), current_index); + auto signal_next = rewriter.create( + loc, findPeaksOpAdaptor.getSignal(), addMapForNext, ValueRange{current_index}); + auto height = rewriter.create( + loc, findPeaksOpAdaptor.getHeight(), ValueRange{}); + + //%cmp_current_prev = arith.cmpf ogt, %signal_current, %signal_prev : f64 + //%cmp_current_next = arith.cmpf ogt, %signal_current, %signal_next : f64 + //%cmp_current_height = arith.cmpf oge, %signal_current, %signal_next : f64 + auto cmp_current_prev = rewriter.create( + loc, arith::CmpFPredicate::OGT, signal_current, signal_prev); + auto cmp_current_next = rewriter.create( + loc, arith::CmpFPredicate::OGT, signal_current, signal_next); + auto cmp_current_height = rewriter.create( + loc, arith::CmpFPredicate::OGE, signal_current, height); + + //%and_two_cmps = arith.andi %cmp_current_prev, %cmp_current_next : index + //%and_three_cmps = arith.andi %and_two_cmps, cmp_current_height : index + auto and_two_cmps = rewriter.create( + loc, cmp_current_prev, cmp_current_next); + auto and_three_cmps = rewriter.create( + loc, and_two_cmps, cmp_current_height); + + //scf.if %and_three_cmps { + auto firstIfOp = rewriter.create(loc, and_three_cmps, false /* else=1 */); + rewriter.setInsertionPointToStart(firstIfOp.thenBlock()); + + //%peaks_count = affine.load %alloc_peaks_count[] : memref + //%cmp_new_peak = arith.cmpi eq, %peaks_count, %cst_zero_index : index + auto peaks_count = rewriter.create( + loc, alloc_peaks_count, ValueRange{}); + auto cmp_new_peak = rewriter.create( + loc, arith::CmpIPredicate::eq, peaks_count, constant_index_zero); + + //scf.if %cmp_new_peak { + // memref.store %current_index, %alloc_peaks[%peaks_count] : memref<10xindex> + // %peaks_count_inc = arith.addi %peaks_count, %cst_one_index : index + // affine.store %peaks_count_inc, %alloc_peaks_count[] : memref + //} + auto secondIfOp = rewriter.create(loc, cmp_new_peak, true /* else=1 */); + rewriter.setInsertionPointToStart(secondIfOp.thenBlock()); + //index to f64 + Value current_index_to_ui = rewriter.create( + loc, rewriter.getIntegerType(32), current_index); + Value current_index_to_f64 = + rewriter.create(loc, rewriter.getF64Type(), current_index_to_ui); + rewriter.create(loc, current_index_to_f64, alloc_output, ValueRange{peaks_count}); + auto peaks_count_inc = rewriter.create(loc, peaks_count, constant_index_one); + rewriter.create(loc, peaks_count_inc, alloc_peaks_count, ValueRange{}); + + /* + else { + %last_peaks_count = arith.subi %peaks_count, %cst_one_index : index + %last_peak_index = memref.load %alloc_peaks[%last_peaks_count] : memref<10xindex> + %subtract_current_index_last_peak = arith.subi %current_index, %last_peak_index : index + %cmp_sub_distance = arith.cmpi sge, %subtract_current_index_last_peak, %distance : index + */ + rewriter.setInsertionPointToStart(secondIfOp.elseBlock()); + //auto last_peak_index = rewriter.create(loc, alloc_output, addMapForPrev, ValueRange{peaks_count}); + //HWISOO: It does not work since it gives "error: 'affine.load' op index must be a valid dimension or symbol identifier" here. + Value last_peaks_count = rewriter.create(loc, peaks_count, constant_index_one); + auto last_peak_index_fp = rewriter.create(loc, alloc_output, ValueRange{last_peaks_count}); + //f64 to index + Value last_peak_index_ui = + rewriter.create(loc, rewriter.getIntegerType(32), last_peak_index_fp); + Value last_peak_index = rewriter.create( + loc, rewriter.getIndexType(), last_peak_index_ui); + Value subtract_current_index_last_peak = rewriter.create(loc, current_index, last_peak_index); + auto cmp_sub_distance = rewriter.create( + loc, arith::CmpIPredicate::sge, subtract_current_index_last_peak, distance); + + /* + scf.if %cmp_sub_distance { + memref.store %current_index, %alloc_peaks[%peaks_count] : memref<10xindex> + %peaks_count_inc = arith.addi %peaks_count, %cst_one_index : index + affine.store %peaks_count_inc, %alloc_peaks_count[] : memref + } + } + */ + auto thirdIfOp = rewriter.create(loc, cmp_sub_distance, true /* else=1 */); + rewriter.setInsertionPointToStart(thirdIfOp.thenBlock()); + //index to f64 + Value current_index_to_ui_2 = rewriter.create( + loc, rewriter.getIntegerType(32), current_index); + Value current_index_to_f64_2 = + rewriter.create(loc, rewriter.getF64Type(), current_index_to_ui_2); + rewriter.create(loc, current_index_to_f64_2, alloc_output, ValueRange{peaks_count}); + auto peaks_count_inc_2 = rewriter.create(loc, peaks_count, constant_index_one); + rewriter.create(loc, peaks_count_inc_2, alloc_peaks_count, ValueRange{}); + + rewriter.setInsertionPointAfter(forOpSignal); + + /* Setting last element of the output as the count of peaks. + Note that last-last ([-2]) should be always -1. */ + auto peaks_count_final = rewriter.create(loc, alloc_peaks_count, ValueRange{}); + //index to f64 + Value peaks_count_final_to_ui = rewriter.create( + loc, rewriter.getIntegerType(32), peaks_count_final); + Value peaks_count_final_to_f64 = + rewriter.create(loc, rewriter.getF64Type(), peaks_count_final_to_ui); + rewriter.create(loc, peaks_count_final_to_f64, alloc_output, addMapForPrev, ValueRange{constant_len_singal}); + + + rewriter.replaceOp(op, alloc_output); + + return success(); + } +}; + + + + + + //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: Unary operations //===----------------------------------------------------------------------===// @@ -7887,7 +8094,7 @@ void ToyToAffineLoweringPass::runOnOperation() { FIRFilterYSymmOptimizedOpLowering, FFT1DRealSymmOpLowering, FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering, ShiftRightOpLowering, MatmulOpLowering, - ThresholdUpOpLowering, QamModulateRealOpLowering, QamModulateImgOpLowering,QamDemodulateOpLowering>(&getContext()); + ThresholdUpOpLowering, QamModulateRealOpLowering, QamModulateImgOpLowering, QamDemodulateOpLowering, FindPeaksOpLowering>(&getContext()); // With the target and rewrite patterns defined, we can now attempt the // conversion. The conversion will signal failure if any of our `illegal` diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index a1e954ce9bf7..e68532eec7f6 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -408,6 +408,16 @@ class MLIRGenImpl { } return builder.create(location, operands[0]); } + + // FindPeaks Op + if(callee == "find_peaks"){ + if(call.getArgs().size() != 3){ + emitError(location, "MLIR codegen encountered an error: dsp.find_peaks " + "accepts only 3 arguments: signal, height, and distance"); + return nullptr; + } + return builder.create(location, operands[0], operands[1], operands[2]); + } // Shift right Op if (callee == "shiftRight") { From cee6a35fa7d029a3ae73b25220f06b2575f3ebc1 Mon Sep 17 00:00:00 2001 From: Atharva Khedkar <55466743+AtharvaKhedkar@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:56:57 -0700 Subject: [PATCH 14/45] Add 12 benchmarks (#18) * add 12 benchmarks --- .gitignore | 4 + .../BenchmarkTest/CCode/ResultScript.py | 8 +- .../BenchmarkTest/CCode/echocancelling.c | 4 +- .../CCode/lowPassFIRFilterDesign.c | 75 ++++--- .../BenchmarkTest/CCode/noisecancelling.c | 201 +++++++++--------- .../BenchmarkTest/CountLinesFile.py | 143 ++++++++++--- .../DSP-DSL/ResultScript.py} | 12 +- .../DSP-DSL}/audioCompression.py | 11 +- .../DSP-DSL}/audioEqualizer.py | 2 +- .../DSP-DSL}/echocancelling.py | 2 +- .../BenchmarkTest/DSP-DSL/energyOfSignal.py | 21 ++ .../DSP-DSL}/hearingAid.py | 0 .../DSP-DSL}/lowPassFIRFilterDesign.py | 2 +- .../DSP-DSL}/lowPassFull.py | 0 .../DSP-DSL}/noisecancelling.py | 6 +- .../DSP-DSL}/periodogram2Conv1.py | 2 +- .../DSP-DSL}/underWaterCommunication.py | 0 .../DSP-DSL}/vibrationAnalysis.py | 2 +- .../DSP-DSL}/voiceActivityDetection.py | 0 .../BenchmarkTest/Matlab/ResultScript.py | 145 +++++++++++++ .../BenchmarkTest/Matlab/audioCompression.m | 71 +++++++ .../BenchmarkTest/Matlab/audioEqualizer.m | 57 +++++ .../BenchmarkTest/Matlab/echoCancelling.m | 36 ++++ .../BenchmarkTest/Matlab/energyOfSignal.m | 22 ++ .../BenchmarkTest/Matlab/hearingAid.m | 45 ++++ .../Matlab/lowPassFIRFilterDesign.m | 48 +++++ .../BenchmarkTest/Matlab/lowPassFull.m | 40 ++++ .../BenchmarkTest/Matlab/noiseCancelling.m | 32 +++ .../BenchmarkTest/Matlab/periodogram.m | 20 ++ .../Matlab/underWaterCommunication.m | 79 +++++++ .../BenchmarkTest/Matlab/vibrationAnalysis.m | 39 ++++ .../Matlab/voiceActivityDetection.m | 57 +++++ .../BenchmarkTest/PyDSL/periodogram2Conv.py | 51 ----- .../ExtractOpName.py | 0 .../HammingWindow.py | 0 .../LMSNoiseFilter.py | 0 .../PyDSL}/FIRFilterHammingOpt.py | 0 .../PyDSL}/ScriptForMlirAffine.py | 0 .../PyDSL}/ScriptForSingleRun.py | 0 .../PyDSL/audioCompression.py | 0 .../PyDSL}/back2backDelay.py | 0 .../PyDSL/energyOfSignal.py | 0 .../PyDSL}/firFilter10.py | 0 .../PyDSL/lowPassFIRFilterDesign1.py | 0 .../PyDSL/lowPassFull1.py | 0 .../PyDSL/noisecancelling.py | 0 .../PyDSL}/periodogram2Conv.py | 0 .../Quantization.py | 0 .../TryHearingAid copy.py | 0 .../TryHearingAid.py | 0 .../audioEqualizer.py | 0 .../bandPassfilter.py | 0 .../filterDesign.py | 0 .../hearingAid.py | 0 .../highPassfilter.py | 0 .../lmsNoiseCancelling.py | 0 .../lowPassFilterApp.py | 0 .../periodogramHelp.py | 0 .../periodogramHelp2.py | 0 .../{Results => HelperScripts}/ScriptSteps.py | 0 .../generate_dense_inputs.py | 0 .../matlab_result.py | 0 .../working_slidingwind.py | 0 .../Output/TryDSPApps/Results/.gitignore | 1 - .../Results/TryResultScript/.gitignore | 3 - .../Results/TryResultScript/EnergyOfSignal.py | 40 ---- 66 files changed, 1005 insertions(+), 276 deletions(-) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript/ScriptForCases.py => BenchmarkTest/DSP-DSL/ResultScript.py} (94%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => BenchmarkTest/DSP-DSL}/audioCompression.py (86%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => BenchmarkTest/DSP-DSL}/audioEqualizer.py (96%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => BenchmarkTest/DSP-DSL}/echocancelling.py (91%) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/energyOfSignal.py rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => BenchmarkTest/DSP-DSL}/hearingAid.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => BenchmarkTest/DSP-DSL}/lowPassFIRFilterDesign.py (98%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => BenchmarkTest/DSP-DSL}/lowPassFull.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => BenchmarkTest/DSP-DSL}/noisecancelling.py (85%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => BenchmarkTest/DSP-DSL}/periodogram2Conv1.py (97%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => BenchmarkTest/DSP-DSL}/underWaterCommunication.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => BenchmarkTest/DSP-DSL}/vibrationAnalysis.py (95%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => BenchmarkTest/DSP-DSL}/voiceActivityDetection.py (100%) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/ResultScript.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/audioCompression.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/audioEqualizer.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/echoCancelling.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/energyOfSignal.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/hearingAid.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/lowPassFIRFilterDesign.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/lowPassFull.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/noiseCancelling.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/periodogram.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/underWaterCommunication.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/vibrationAnalysis.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/voiceActivityDetection.m delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/periodogram2Conv.py rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/ExtractOpName.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/HammingWindow.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/LMSNoiseFilter.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => ExamplePythonApps/PyDSL}/FIRFilterHammingOpt.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => ExamplePythonApps/PyDSL}/ScriptForMlirAffine.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => ExamplePythonApps/PyDSL}/ScriptForSingleRun.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/PyDSL/audioCompression.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => ExamplePythonApps/PyDSL}/back2backDelay.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/PyDSL/energyOfSignal.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => ExamplePythonApps/PyDSL}/firFilter10.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/PyDSL/lowPassFIRFilterDesign1.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/PyDSL/lowPassFull1.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/PyDSL/noisecancelling.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results/TryResultScript => ExamplePythonApps/PyDSL}/periodogram2Conv.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/Quantization.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/TryHearingAid copy.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/TryHearingAid.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/audioEqualizer.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/bandPassfilter.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/filterDesign.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/hearingAid.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/highPassfilter.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/lmsNoiseCancelling.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/lowPassFilterApp.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/periodogramHelp.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{BenchmarkTest => ExamplePythonApps}/periodogramHelp2.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results => HelperScripts}/ScriptSteps.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results => HelperScripts}/generate_dense_inputs.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results => HelperScripts}/matlab_result.py (100%) rename mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/{Results => HelperScripts}/working_slidingwind.py (100%) delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/.gitignore delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/.gitignore delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/EnergyOfSignal.py diff --git a/.gitignore b/.gitignore index 8b3186a6eea4..8ef7fa61c46b 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,7 @@ pythonenv* # automodapi puts generated documentation files here. /lldb/docs/python_api/ mlir_opt_helper.txt +mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/Output/* +mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/Output/* +mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/Output/* +mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Output/* diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/ResultScript.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/ResultScript.py index 6eeb2301b2df..fdd325ebecef 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/ResultScript.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/ResultScript.py @@ -16,11 +16,9 @@ # Path to the input file # Apps = "lowPassFIRFilterDesign.c", "noisecancelling.c" , "echocancelling.c", "hearingAid.c", "audioEqualizer.c", "vibrationAnalysis.c", "underWaterCommunication.c", "voiceActivityDetection.c" -input_file_path = "voiceActivityDetection.c" -BasePathForLLVM = "DSP_MLIR" -OutputScriptPath = ( - "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/PythonCodeForTest/CCode/" -) +input_file_path = "vibrationAnalysis.c" +BasePathForLLVM = "/home/local/ASURITE/apkhedka/ForLLVM/" +OutputScriptPath = "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/" # OutputPath = BasePathForLLVM + "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/Output/" print(f"Running Application {input_file_path}") # Construct full output path diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/echocancelling.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/echocancelling.c index 0ce15fd4a9cb..d9b9ebbaba0e 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/echocancelling.c +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/echocancelling.c @@ -97,8 +97,8 @@ int main() { lmsFilterResponse(y, noisy_sig, clean_sig, mu, filterSize, INPUT_LENGTH); - // Print result (for demonstration purposes) - for (int i = 0; i < INPUT_LENGTH && i < 10; i++) { // Limit print to first few samples + // Print result + for (int i = 0; i < INPUT_LENGTH; i++) { printf("%f\n", y[i]); } diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/lowPassFIRFilterDesign.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/lowPassFIRFilterDesign.c index 79e8d376466b..68c0dc26edaf 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/lowPassFIRFilterDesign.c +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/lowPassFIRFilterDesign.c @@ -1,14 +1,16 @@ -#include #include +#include #include -#define INPUT_LENGTH 100000001 -#define PI M_PI -#define FC1 500 +#define INPUT_LENGTH 100000000 +#define PI M_PI #define FS 8000 +#define FC1 500 +#define FC2 600 +#define FC3 1000 -double* hamming(int length) { - double* window = malloc(length * sizeof(double)); +double *hamming(int length) { + double *window = malloc(length * sizeof(double)); if (!window) { perror("Memory allocation failed in hamming"); exit(EXIT_FAILURE); @@ -19,57 +21,80 @@ double* hamming(int length) { return window; } -double* lowPassFIRFilter(double wc, int length) { - double* filter = malloc(length * sizeof(double)); +double *highPassFIRFilter(double wc, int length) { + double *filter = malloc(length * sizeof(double)); if (!filter) { - perror("Memory allocation failed in lowPassFIRFilter"); + perror("Memory allocation failed in highPassFIRFilter"); exit(EXIT_FAILURE); } - int mid = (length - 1) / 2; for (int n = 0; n < length; n++) { if (n == mid) { - filter[n] = wc / PI; + filter[n] = 1 - (wc / PI); } else { - filter[n] = sin(wc * (n - mid)) / (PI * (n - mid)); + filter[n] = -sin(wc * (n - mid)) / (PI * (n - mid)); } } return filter; } -void elementWiseMultiplication(double* output, const double* array1, const double* array2, int length) { +void elementWiseMultiplication(double *output, const double *array1, + const double *array2, int length) { for (int i = 0; i < length; i++) { output[i] = array1[i] * array2[i]; } } -double getElemAtIndx(const double* array, int index) { +double getElemAtIndx(const double *array, int index) { return array[index]; } int main() { double wc1 = 2 * PI * FC1 / FS; + double wc2 = 2 * PI * FC2 / FS; + double wc3 = 2 * PI * FC3 / FS; - double* lpf = lowPassFIRFilter(wc1, INPUT_LENGTH); - double* hamming_window = hamming(INPUT_LENGTH); + double *hamming_window = hamming(INPUT_LENGTH); - double* lpf_w = malloc(INPUT_LENGTH * sizeof(double)); - if (!lpf_w) { - perror("Memory allocation failed for lpf_w"); - free(lpf); - free(hamming_window); + double *hpf1 = highPassFIRFilter(wc1, INPUT_LENGTH); + double *hpf_w1 = malloc(INPUT_LENGTH * sizeof(double)); + if (!hpf_w1) { + perror("Memory allocation failed for hpf_w1"); exit(EXIT_FAILURE); } + elementWiseMultiplication(hpf_w1, hpf1, hamming_window, INPUT_LENGTH); - elementWiseMultiplication(lpf_w, lpf, hamming_window, INPUT_LENGTH); + double *hpf2 = highPassFIRFilter(wc2, INPUT_LENGTH); + double *hpf_w2 = malloc(INPUT_LENGTH * sizeof(double)); + if (!hpf_w2) { + perror("Memory allocation failed for hpf_w2"); + exit(EXIT_FAILURE); + } + elementWiseMultiplication(hpf_w2, hpf2, hamming_window, INPUT_LENGTH); + + double *hpf3 = highPassFIRFilter(wc3, INPUT_LENGTH); + double *hpf_w3 = malloc(INPUT_LENGTH * sizeof(double)); + if (!hpf_w3) { + perror("Memory allocation failed for hpf_w3"); + exit(EXIT_FAILURE); + } + elementWiseMultiplication(hpf_w3, hpf3, hamming_window, INPUT_LENGTH); - double final1 = getElemAtIndx(lpf_w, 6); + double final1 = getElemAtIndx(hpf_w1, 6); + double final2 = getElemAtIndx(hpf_w2, 7); + double final3 = getElemAtIndx(hpf_w3, 8); printf("%f\n", final1); + printf("%f\n", final2); + printf("%f\n", final3); - free(lpf); free(hamming_window); - free(lpf_w); + free(hpf1); + free(hpf2); + free(hpf3); + free(hpf_w1); + free(hpf_w2); + free(hpf_w3); return 0; } \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/noisecancelling.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/noisecancelling.c index 184652a9bf5d..8d2402f8ccc5 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/noisecancelling.c +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/noisecancelling.c @@ -1,117 +1,120 @@ +#include #include #include -#include #define INPUT_LENGTH 100000000 -void getRangeOfVector(double* vector, double start, int length, double increment) { - for (int i = 0; i < length; i++) { - vector[i] = start + i * increment; - } +void getRangeOfVector(double *vector, double start, int length, + double increment) { + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; + } } -void gain(double* output, double* input, double multiplier, int length) { - for (int i = 0; i < length; i++) { - output[i] = input[i] * multiplier; - } +void gain(double *output, double *input, double multiplier, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * multiplier; + } } -void sine(double* output, double* input, int length) { - for (int i = 0; i < length; i++) { - output[i] = sin(input[i]); - } +void sine(double *output, double *input, int length) { + for (int i = 0; i < length; i++) { + output[i] = sin(input[i]); + } } -void add(double* output, double* input1, double* input2, int length) { - for (int i = 0; i < length; i++) { - output[i] = input1[i] + input2[i]; - } +void add(double *output, double *input1, double *input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] + input2[i]; + } } -void lmsFilterResponse(double* output, double* noisy_sig, double* clean_sig, double mu, int filterSize, int length) { - double w[32] = {0}; - for (int n = 0; n < length; n++) { - double y = 0; - for (int i = 0; i < filterSize; i++) { - if (n - i >= 0) { - y += w[i] * noisy_sig[n - i]; - } - } - double e = clean_sig[n] - y; - for (int i = 0; i < filterSize; i++) { - if (n - i >= 0) { - w[i] += mu * e * noisy_sig[n - i]; - } - } - output[n] = y; +void lmsFilterResponse(double *output, double *noisy_sig, double *clean_sig, + double mu, int filterSize, int length) { + double w[32] = {0}; + for (int n = 0; n < length; n++) { + double y = 0; + for (int i = 0; i < filterSize; i++) { + if (n - i >= 0) { + y += w[i] * noisy_sig[n - i]; + } + } + double e = clean_sig[n] - y; + for (int i = 0; i < filterSize; i++) { + if (n - i >= 0) { + w[i] += mu * e * noisy_sig[n - i]; + } } + output[n] = y; + } } int main() { - // Allocate memory dynamically - double* t = (double*)malloc(INPUT_LENGTH * sizeof(double)); - double* getSinDuration = (double*)malloc(INPUT_LENGTH * sizeof(double)); - double* clean_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); - double* getNoiseSinDuration = (double*)malloc(INPUT_LENGTH * sizeof(double)); - double* noise = (double*)malloc(INPUT_LENGTH * sizeof(double)); - double* noise1 = (double*)malloc(INPUT_LENGTH * sizeof(double)); - double* noisy_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); - double* y = (double*)malloc(INPUT_LENGTH * sizeof(double)); - double* sol = (double*)malloc(INPUT_LENGTH * sizeof(double)); - - // Check if memory allocation was successful - if (!t || !getSinDuration || !clean_sig || !getNoiseSinDuration || !noise || !noise1 || !noisy_sig || !y || !sol) { - perror("Memory allocation failed"); - free(t); - free(getSinDuration); - free(clean_sig); - free(getNoiseSinDuration); - free(noise); - free(noise1); - free(noisy_sig); - free(y); - free(sol); - exit(EXIT_FAILURE); - } - - // Signal processing steps - getRangeOfVector(t, 0, INPUT_LENGTH, 0.000125); - - double f_sig = 500; - double pi = 3.14159265359; - gain(getSinDuration, t, 2 * pi * f_sig, INPUT_LENGTH); - - sine(clean_sig, getSinDuration, INPUT_LENGTH); - - double f_noise = 3000; - gain(getNoiseSinDuration, t, 2 * pi * f_noise, INPUT_LENGTH); - - sine(noise, getNoiseSinDuration, INPUT_LENGTH); - - gain(noise1, noise, 0.5, INPUT_LENGTH); - - add(noisy_sig, clean_sig, noise1, INPUT_LENGTH); - - // LMS filter response - lmsFilterResponse(y, noisy_sig, clean_sig, 0.01, 32, INPUT_LENGTH); - - gain(sol, y, 10, INPUT_LENGTH); - - // Print the filtered signal - for (int i = 0; i < INPUT_LENGTH; i++) { - printf("%f\n", sol[i]); - } - - // Free allocated memory at the end - free(t); - free(getSinDuration); - free(clean_sig); - free(getNoiseSinDuration); - free(noise); - free(noise1); - free(noisy_sig); - free(y); - free(sol); - - return 0; + // Allocate memory dynamically + double *t = (double *)malloc(INPUT_LENGTH * sizeof(double)); + double *getSinDuration = (double *)malloc(INPUT_LENGTH * sizeof(double)); + double *clean_sig = (double *)malloc(INPUT_LENGTH * sizeof(double)); + double *getNoiseSinDuration = (double *)malloc(INPUT_LENGTH * sizeof(double)); + double *noise = (double *)malloc(INPUT_LENGTH * sizeof(double)); + double *noise1 = (double *)malloc(INPUT_LENGTH * sizeof(double)); + double *noisy_sig = (double *)malloc(INPUT_LENGTH * sizeof(double)); + double *y = (double *)malloc(INPUT_LENGTH * sizeof(double)); + double *sol = (double *)malloc(INPUT_LENGTH * sizeof(double)); + + // Check if memory allocation was successful + if (!t || !getSinDuration || !clean_sig || !getNoiseSinDuration || !noise || + !noise1 || !noisy_sig || !y || !sol) { + perror("Memory allocation failed"); + free(t); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + free(noise1); + free(noisy_sig); + free(y); + free(sol); + exit(EXIT_FAILURE); + } + + // Signal processing steps + getRangeOfVector(t, 0, INPUT_LENGTH, 0.000125); + + double f_sig = 500; + double pi = 3.14159265359; + gain(getSinDuration, t, 2 * pi * f_sig, INPUT_LENGTH); + + sine(clean_sig, getSinDuration, INPUT_LENGTH); + + double f_noise = 3000; + gain(getNoiseSinDuration, t, 2 * pi * f_noise, INPUT_LENGTH); + + sine(noise, getNoiseSinDuration, INPUT_LENGTH); + + gain(noise1, noise, 0.5, INPUT_LENGTH); + + add(noisy_sig, clean_sig, noise1, INPUT_LENGTH); + + // LMS filter response + lmsFilterResponse(y, noisy_sig, clean_sig, 0.01, 32, INPUT_LENGTH); + + gain(sol, y, 10, INPUT_LENGTH); + + // Print the filtered signal + for (int i = 0; i < INPUT_LENGTH; i++) { + printf("%f\n", sol[i]); + } + + // Free allocated memory at the end + free(t); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + free(noise1); + free(noisy_sig); + free(y); + free(sol); + + return 0; } \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CountLinesFile.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CountLinesFile.py index 565e5d680e96..283069c90136 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CountLinesFile.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CountLinesFile.py @@ -1,60 +1,145 @@ import os - -# folder1 = "./CCode" # Replace with your folder path -# Get the current Python file's directory +import pandas as pd current_dir = os.path.dirname(os.path.abspath(__file__)) - -# Specify the folder path relative to the current directory folderC = os.path.join(current_dir, 'CCode') -folderPy = os.path.join(current_dir, 'PyDSL') +folderDSL = os.path.join(current_dir, 'DSP-DSL') # Renamed this folder +folderMatlab = os.path.join(current_dir, 'Matlab') os.makedirs('Output', exist_ok=True) -# Specify the output file path output_fileC = os.path.join(current_dir, 'Output', 'NoOfLinesInC.txt') -output_filePy = os.path.join(current_dir, 'Output', 'NoOfLinesInPy.txt') +output_fileDSL = os.path.join(current_dir, 'Output', 'NoOfLinesInPython.txt') +output_fileMatlab = os.path.join(current_dir, 'Output', 'NoOfLinesInMatlab.txt') def count_non_empty_linesInC(file_path): with open(file_path, 'r') as file: lines = file.readlines() - non_empty_lines = [line for line in lines if line.strip()] - return len(non_empty_lines) + non_empty_code_lines = 0 + in_multiline_comment = False + for line in lines: + stripped_line = line.strip() + if in_multiline_comment: + if '*/' in stripped_line: + in_multiline_comment = False + stripped_line = stripped_line.split('*/', 1)[1] + else: + continue + if stripped_line.startswith('//'): + continue + if '/*' in stripped_line: + if '*/' in stripped_line: + stripped_line = stripped_line.split('/*', 1)[0] + stripped_line.split('*/', 1)[1] + else: + in_multiline_comment = True + stripped_line = stripped_line.split('/*', 1)[0] + if stripped_line: + non_empty_code_lines += 1 + return non_empty_code_lines -def count_valid_code_linesInPyFile(file_path): +def count_valid_code_lines_in_dsl(file_path): valid_code_lines = 0 - with open(file_path, 'r') as file: for line in file: stripped_line = line.strip() - # Check if the line is not empty and does not start with a comment if stripped_line and not stripped_line.startswith('#'): valid_code_lines += 1 + return valid_code_lines +def count_valid_code_lines_in_matlab(file_path): + valid_code_lines = 0 + with open(file_path, 'r') as file: + in_multiline_comment = False + for line in file: + stripped_line = line.strip() + if in_multiline_comment: + if stripped_line.endswith('%}'): + in_multiline_comment = False + continue + if stripped_line.startswith('%{'): + in_multiline_comment = True + continue + if stripped_line and not stripped_line.startswith('%'): + valid_code_lines += 1 return valid_code_lines + +def count_lines_across_languages(): + line_counts = {} + if os.path.exists(folderC): + for filename in sorted(os.listdir(folderC)): + file_path = os.path.join(folderC, filename) + if os.path.isfile(file_path) and filename.endswith('.c'): + count = count_non_empty_linesInC(file_path) + line_counts[filename] = {'lines_in_c': count, 'lines_in_dsl': 0, 'lines_in_matlab': 0} + + # Count Python files + if os.path.exists(folderDSL): + for filename in sorted(os.listdir(folderDSL)): + file_path = os.path.join(folderDSL, filename) + if os.path.isfile(file_path) and filename.endswith('.py'): + count = count_valid_code_lines_in_dsl(file_path) + if filename in line_counts: + line_counts[filename]['lines_in_dsl'] = count + else: + line_counts[filename] = {'lines_in_c': 0, 'lines_in_dsl': count, 'lines_in_matlab': 0} + + # Count MATLAB files + if os.path.exists(folderMatlab): + for filename in sorted(os.listdir(folderMatlab)): + file_path = os.path.join(folderMatlab, filename) + if os.path.isfile(file_path) and filename.endswith('.m'): + count = count_valid_code_lines_in_matlab(file_path) + if filename in line_counts: + line_counts[filename]['lines_in_matlab'] = count + else: + line_counts[filename] = {'lines_in_c': 0, 'lines_in_dsl': 0, 'lines_in_matlab': count} + return line_counts -def list_files_and_write_line_counts(folder, output_path): - # List files in the folder and sort them by filename - files = sorted(os.listdir(folder)) - with open(output_path, 'w') as output: - for filename in files: - file_path = os.path.join(folder, filename) - if os.path.isfile(file_path) and filename.endswith('.c'): # Check if it's a text file - line_count = count_non_empty_linesInC(file_path) - output.write(f"{filename}: \t{line_count} \n") +def create_consolidated_table(): + line_counts = count_lines_across_languages() + + # Create a DataFrame + df = pd.DataFrame.from_dict(line_counts, orient='index') + + # Reset index to make filename a column + df.reset_index(inplace=True) + df.rename(columns={'index': 'filename'}, inplace=True) + + # Reorder columns + df = df[['filename', 'lines_in_dsl', 'lines_in_c', 'lines_in_matlab']] + + # Fill NaN values with 0 + df.fillna(0, inplace=True) + + # Convert line count columns to integers + for col in ['lines_in_dsl', 'lines_in_c', 'lines_in_matlab']: + df[col] = df[col].astype(int) + + return df -def list_files_and_write_line_countsPy(folder, output_path): - # List files in the folder and sort them by filename +def list_files_and_write_line_counts(folder, output_path, count_function, extension): files = sorted(os.listdir(folder)) with open(output_path, 'w') as output: for filename in files: file_path = os.path.join(folder, filename) - if os.path.isfile(file_path) and filename.endswith('.py'): # Check if it's a text file - line_count = count_valid_code_linesInPyFile(file_path) + if os.path.isfile(file_path) and filename.endswith(extension): + line_count = count_function(file_path) output.write(f"{filename}: \t{line_count}\n") +if __name__ == "__main__": + # Create the consolidated table + consolidated_table = create_consolidated_table() + + # Save the consolidated table to a CSV file + output_file = os.path.join('Output', 'consolidated_lines_of_code.csv') + consolidated_table.to_csv(output_file, index=False) -# Call the function -list_files_and_write_line_counts(folderC, output_fileC) -list_files_and_write_line_countsPy(folderPy, output_filePy) + # Display the table + print(consolidated_table) + # Output file paths + print(f"\nConsolidated table saved to: {output_file}") + + list_files_and_write_line_counts(folderC, output_fileC, count_non_empty_linesInC, '.c') + list_files_and_write_line_counts(folderDSL, output_fileDSL, count_valid_code_lines_in_dsl, '.py') + list_files_and_write_line_counts(folderMatlab, output_fileMatlab, count_valid_code_lines_in_matlab, '.m') \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/ScriptForCases.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/ResultScript.py similarity index 94% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/ScriptForCases.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/ResultScript.py index 1de7b78baa28..36c4d79100cc 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/ScriptForCases.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/ResultScript.py @@ -16,11 +16,11 @@ # Path to the input file # Apps = "hearingAid.py" , "lowPassFull.py" , " audioCompression.py" , -# "back2backDelay.py" , "lowPassFIRFilterDesign.py" , EnergyOfSignal.py, periodogram2Conv1.py, audioEqualizer.py -input_file_path = "voiceActivityDetection.py" -BasePathForLLVM = "DSP_MLIR" +# "back2backDelay.py" , "lowPassFIRFilterDesign.py" , "EnergyOfSignal.py", "periodogram2Conv1.py", "audioEqualizer.py", "vibrationAnalysis.py" +input_file_path = "vibrationAnalysis.py" +BasePathForLLVM = "/home/local/ASURITE/apkhedka/ForLLVM/" OutputScriptPath = ( - "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/" + "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/" ) # OutputPath = BasePathForLLVM + "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/Output/" print(f"Running Application {input_file_path}") @@ -48,7 +48,7 @@ "40K": 40000, "50K": 50000, "100K": 100000, - "1M": 1000000, + "1M": 1000000, "10M": 10000000, "20M": 20000000, "30M": 30000000, @@ -102,7 +102,7 @@ # Update the specific line in the file # print("Updating for {}".format(value)) # print("\n") - value2 = 1/value + value2 = 1 / value print("\n{}".format(key), end="\t") with open(input_file_path, "w") as file: for line in lines: diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioCompression.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/audioCompression.py similarity index 86% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioCompression.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/audioCompression.py index 489044fe81c4..117059a332be 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioCompression.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/audioCompression.py @@ -9,7 +9,7 @@ def main() { # var a10 = [ 3.2, 1.5, 0.8, 2.9, 4.5,10 , 0,5,5.5, 1.1]; # var a10 = getRangeOfVector(3.2, 10, 1); - var input = getRangeOfVector(0, 50000, 1); + var input = getRangeOfVector(0, 40000, 1); var nlevels = 16; #powerOf2 var min = 0; var max = 8; @@ -31,14 +31,13 @@ def main() { var QuantOutReal = quantization(GetThresholdReal , nlevels, max, min); var QuantOutImg = quantization(GetThresholdImg , nlevels, max, min); - print(QuantOutReal); - print(QuantOutImg); #RLE var rLEOutReal = runLenEncoding(QuantOutReal); var rLEOutImg = runLenEncoding(QuantOutImg); - - # print(rLEOutReal); - # print(rLEOutImg); + var final1 = getElemAtIndx(rLEOutReal , [3]); + var final2 = getElemAtIndx(rLEOutImg , [2]); + print(final1); + print(final2); } diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioEqualizer.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/audioEqualizer.py similarity index 96% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioEqualizer.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/audioEqualizer.py index 209734bc6020..174a5e4bc609 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/audioEqualizer.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/audioEqualizer.py @@ -5,7 +5,7 @@ def main() { # var input = [1,2,3,4,5]; - var input = getRangeOfVector(0, 100000000, 1); + var input = getRangeOfVector(0, 30000000, 1); var pi = 3.14159265359; var fc = 300; var Fs = 8000; diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/echocancelling.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/echocancelling.py similarity index 91% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/echocancelling.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/echocancelling.py index f6f3e55d8faa..3d85df2a79e4 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/echocancelling.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/echocancelling.py @@ -2,7 +2,7 @@ def main() { var fs = 8000; # var step = 1/8000; # print(step); - var input = getRangeOfVector(0, 100000000, 0.000125); + var input = getRangeOfVector(0, 100000000, 1); var f_sig = 500; var pi = 3.14159265359; var getMultiplier = 2 * pi * f_sig; diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/energyOfSignal.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/energyOfSignal.py new file mode 100644 index 000000000000..9f17b3ef81da --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/energyOfSignal.py @@ -0,0 +1,21 @@ + +def main() { + + var input = getRangeOfVector(0, 40000, 1); + #calculate x[l] + + + # var fft1 = fft1d(input); + #calculate fft : fft1 = fft(conv1) + var fft_real = fft1dreal(input); + var fft_img = fft1dimg(input); + var sq_abs = square(fft_real) + square(fft_img) ; + # var sq_abs = square(fft1); + # sum = sum(sq_abs) + var sum1 = sum(sq_abs); + # res = gain(sum , 1/N) + var len1 = len(input); + var res = sum1 / len1; + print(res); +} + diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/hearingAid.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/hearingAid.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/hearingAid.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/hearingAid.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFIRFilterDesign.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/lowPassFIRFilterDesign.py similarity index 98% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFIRFilterDesign.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/lowPassFIRFilterDesign.py index 267aaea41edc..49b7a1330827 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFIRFilterDesign.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/lowPassFIRFilterDesign.py @@ -8,7 +8,7 @@ def main() { # var a10 = getRangeOfVector(0, 400, 0.000125); # var orig = sin(a10); - var N = 11 ; + var N = 100000001 ; # for cut-off freq var pi = 3.14159265359; diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFull.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/lowPassFull.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/lowPassFull.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/lowPassFull.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/noisecancelling.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/noisecancelling.py similarity index 85% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/noisecancelling.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/noisecancelling.py index 2b05325f4c92..181b9d823a49 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/noisecancelling.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/noisecancelling.py @@ -2,7 +2,7 @@ def main() { var fs = 8000; # var step = 1/8000; # print(step); - var input = getRangeOfVector(0, 100000000, 0.000125); + var input = getRangeOfVector(0, 100000000, 1); var f_sig = 500; var pi = 3.14159265359; var getMultiplier = 2 * pi * f_sig; @@ -23,8 +23,6 @@ def main() { var mu = 0.01; var filterSize = 32; var y = lmsFilterResponse(noisy_sig, clean_sig, mu, filterSize); - var final1 = getElemAtIndx(y , [6]); - print(final1); - + print(y); } diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/periodogram2Conv1.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/periodogram2Conv1.py similarity index 97% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/periodogram2Conv1.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/periodogram2Conv1.py index e84f0c82b509..5ba84b852663 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/periodogram2Conv1.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/periodogram2Conv1.py @@ -14,7 +14,7 @@ def main() { #size 10 # var a10 = [ 10,20,30,40,50,60,70,80,90,100]; - var input = getRangeOfVector(0, 20000, 1); + var input = getRangeOfVector(0, 30000, 1); # var input = [1,2,3,4]; # print(a10); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/underWaterCommunication.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/underWaterCommunication.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/underWaterCommunication.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/underWaterCommunication.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/vibrationAnalysis.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/vibrationAnalysis.py similarity index 95% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/vibrationAnalysis.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/vibrationAnalysis.py index 50a59c502802..2116ba1cb2e4 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/vibrationAnalysis.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/vibrationAnalysis.py @@ -2,7 +2,7 @@ def main() { var fs = 1000; # var step = 1/fs; # print(step); - var input = getRangeOfVector(0, 100000, 1); + var input = getRangeOfVector(0, 30000, 1); var pi = 3.14159265359; var getMultiplier = 2 * pi * 50; # print(getMultiplier); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/voiceActivityDetection.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/voiceActivityDetection.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/voiceActivityDetection.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/voiceActivityDetection.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/ResultScript.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/ResultScript.py new file mode 100644 index 000000000000..c806354a0624 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/ResultScript.py @@ -0,0 +1,145 @@ +import os +import subprocess +import time +import re +# The script does the following +# Input : filename.py +# Output : TimeOfExecution for different IP sizes : +# Steps to run: +# Open a terminal at the path of the script -- +# Run: python ScriptForCases.py #3.11 validated + +# Pseudo-code: +# Iterate for all the input-size & update the input value in file +# Update logic -- change the 2nd parameter of line: var c = getRangeOfVector(init , Count, StepSize) +# Run the respective commands on the file + +# Path to the input file +# Apps = "noiseCancelling.m" , "echoCancelling.m", "periodogram.m", "lowPassFull.m", "hearingAid.m", "lowPassFIRFilterDesign", "energyOfSignal", "audioEqualizer", "audioCompression","vibrationAnalysis", "underWaterCommunication", "voiceActivityDetection" +input_file = "lowPassFIRFilterDesign" +input_file_path = input_file + ".m" +BasePathForLLVM = "/home/local/ASURITE/apkhedka/ForLLVM/" +OutputScriptPath = "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/" +mcc_path ="/home/local/ASURITE/apkhedka/Matlab_Installation/bin/mcc" +mrt_path ="/home/local/ASURITE/apkhedka/Matlab_Runtime/R2024b/" +# OutputPath = BasePathForLLVM + "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/Output/" +print(f"Running Application {input_file_path}") +# Construct full output path +OutputPath = os.path.join(BasePathForLLVM, OutputScriptPath, "Output") + +# Check if the Output folder exists, create it if it doesn't +if not os.path.exists(OutputPath): + os.makedirs(OutputPath) + +# Now OutputPath is ready for use +print("InputPath:{}".format(BasePathForLLVM)) +print(f"OutputPath: {OutputPath}") +# exit() + +# ************ Don't change unless u required +# Define the values dictionary +inputValues = { + "10": 10, + "100": 100, + "1K": 1000, + "10K": 10000, + "20K": 20000, + "30K": 30000, + "40K": 40000, + "50K": 50000, + "100K": 100000, + "1M": 1000000, + "10M": 10000000, + "20M": 20000000, + "30M": 30000000, + "40M": 40000000, + "50M": 50000000, + "100M": 100000000, + # "1B": 1000000000 +} +NoOfIterations = 3 + +def delete_folder_contents(folder_path): + for filename in os.listdir(folder_path): + file_path = os.path.join(folder_path, filename) + try: + if os.path.isfile(file_path) or os.path.islink(file_path): + os.unlink(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + except Exception as e: + print(f'Failed to delete {file_path}. Reason: {e}') + + +with open(input_file_path, "r") as file: + lines = file.readlines() + +print("", end="\t") + + +for key, value in inputValues.items(): + # Update the specific line in the file + # print("Updating for {}".format(value)) + print("\n{}".format(key), end="\t") + with open(input_file_path, "w") as file: + for line in lines: + if line.strip().startswith("INPUT_LENGTH = "): + updated_line = f"INPUT_LENGTH = {value};\n" + file.write(updated_line) + else: + file.write(line) + + command = f"{mcc_path} -m {input_file_path} -d 'Output/' -o {input_file}{key}" + result = subprocess.run(command, shell=True, capture_output=True, text=True) + + # Modify the generated shell script + script_path = f"./Output/run_{input_file}{key}.sh" + # Modify the generated shell script + script_path = f"./Output/run_{input_file}{key}.sh" + with open(script_path, 'r') as file: + script_content = file.readlines() + + # Find the line with the eval command and modify it + for i, line in enumerate(script_content): + if line.strip().startswith('eval'): + script_content[i] = f""" start_time=$(date +%s.%N) + {line.strip()} + end_time=$(date +%s.%N) + execution_time=$(echo "$end_time - $start_time" | bc) + echo "Execution time: $execution_time" +""" + break + + # Write the modified content back to the script + with open(script_path, 'w') as file: + file.writelines(script_content) + + + sum_exe_time = 0 + for i in range(0, NoOfIterations): + try: + subprocess.run("sudo sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches'", shell=True, check=True) + except subprocess.CalledProcessError as exc: + print(exc) + + command2 = f"taskset -c 0 ./Output/run_{input_file}{key}.sh {mrt_path}" + + try: + result = subprocess.run(command2, shell=True, capture_output=True, text=True, check=True) + output = result.stdout + + # Extract execution time from the output + match = re.search(r"Execution time: (\d+\.\d+)", output) + if match: + execution_time = float(match.group(1)) + sum_exe_time += execution_time + else: + print(f"Execution time not found in output: {output}") + except subprocess.CalledProcessError as exc: + print(f"Process failed. Returned {exc.returncode}\n{exc}") + + avg_exe_time = sum_exe_time / NoOfIterations + print(f"{avg_exe_time}", end="\t") + delete_folder_contents("./Output") + + diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/audioCompression.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/audioCompression.m new file mode 100644 index 000000000000..c388ce8f13fa --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/audioCompression.m @@ -0,0 +1,71 @@ +% Constants +INPUT_LENGTH = 100000000; +NLEVELS = 16; +MIN = 0.0; +MAX = 8.0; +THRESHOLD_VAL = 4.0; + +% Function to get range of vector +function output = getRangeOfVector(start, noOfSamples, increment) + output = start + (0:noOfSamples-1) * increment; +end + +% DFT function +function output = dft(input) + N = length(input); + n = 0:N-1; + k = n'; + M = exp(-1j * 2 * pi * k * n / N); + output = M * input(:); +end + +% Threshold function +function output = threshold(input, thresh) + output = input .* (abs(input) >= thresh); +end + +% Quantization function +function output = quantization(input, nlevels, max, min) + step = (max - min) / nlevels; + output = round((input - min) / step) * step + min; +end + +% Run Length Encoding function +function [rle, rleLength] = runLenEncoding(input) + diffs = diff([input(:); NaN]); + runs = find(diffs ~= 0); + lengths = diff([0; runs]); + values = input(runs); + rle = [values, lengths]; + rle = rle'; + rle = rle(:); + rleLength = length(rle); +end + +% Get element at index function +function elem = getElemAtIndx(rle, indx) + elem = rle(indx); +end + +% Main script +input = getRangeOfVector(0, INPUT_LENGTH, 1); + +fft_result = dft(input); + +GetThresholdReal = real(fft_result); +GetThresholdImg = imag(fft_result); + +GetThresholdReal = threshold(GetThresholdReal, THRESHOLD_VAL); +GetThresholdImg = threshold(GetThresholdImg, THRESHOLD_VAL); + +QuantOutReal = quantization(GetThresholdReal, NLEVELS, MAX, MIN); +QuantOutImg = quantization(GetThresholdImg, NLEVELS, MAX, MIN); + +[rLEOutReal, rleLengthReal] = runLenEncoding(QuantOutReal); +[rLEOutImg, rleLengthImg] = runLenEncoding(QuantOutImg); + +final1 = getElemAtIndx(rLEOutReal, 2); +final2 = getElemAtIndx(rLEOutImg, 1); + +fprintf('%f\n', final1); +fprintf('%f\n', final2); \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/audioEqualizer.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/audioEqualizer.m new file mode 100644 index 000000000000..8b3f85c42e18 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/audioEqualizer.m @@ -0,0 +1,57 @@ +% Constants +INPUT_LENGTH = 100000000; +pi = 3.14159265359; +fc = 300; +Fs = 8000; +gainForBass = 2; +gainForMid = 1.5; +gainForTreble = 0.8; +wc = 2 * pi * fc / Fs; +N = 101; + +% Input signal +input = 0:(INPUT_LENGTH-1); + +% Low-pass filter +lpf = lowPassFIRFilter(wc, N); +hamming_window = hamming(N)'; +lpf_w = lpf .* hamming_window; +FIRfilterResponseForLpf = conv(input, lpf_w, 'same'); +gainWithLpf = FIRfilterResponseForLpf * gainForBass; + +% High-pass filter +fc2 = 1500; +wc2 = 2 * pi * fc2 / Fs; +hpf = highPassFIRFilter(wc2, N); +hpf_w = hpf .* hamming_window; +FIRfilterResponseForHpf = conv(input, hpf_w, 'same'); +gainWithHpf = FIRfilterResponseForHpf * gainForTreble; + +% Band-pass filter +lpf2 = lowPassFIRFilter(wc2, N); +lpf2_w = lpf2 .* hamming_window; +bpf_w = lpf2_w - lpf_w; +FIRfilterResponseForBpf = conv(input, bpf_w, 'same'); +gainWithBpf = FIRfilterResponseForBpf * gainForMid; + +% Final audio +final_audio = gainWithLpf + gainWithHpf + gainWithBpf; + +% Print results +fprintf('Element at index 4: %f\n', final_audio(4)); +disp(final_audio); + +% Helper functions +function h = lowPassFIRFilter(wc, length) + n = 0:(length-1); + mid = (length - 1) / 2; + h = zeros(1, length); + h(n ~= mid) = sin(wc * (n(n ~= mid) - mid)) ./ (pi * (n(n ~= mid) - mid)); + h(mid+1) = wc / pi; +end + +function h = highPassFIRFilter(wc, length) + lpf = lowPassFIRFilter(wc, length); + h = -lpf; + h((length+1)/2) = h((length+1)/2) + 1; +end \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/echoCancelling.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/echoCancelling.m new file mode 100644 index 000000000000..2fae6b58462e --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/echoCancelling.m @@ -0,0 +1,36 @@ +% Constants +INPUT_LENGTH = 100000000; +PI = pi; % MATLAB has pi built-in +fs = 8000; +step = 1 / fs; + +% Generate input range +input = (0:step:(INPUT_LENGTH-1)*step)'; + +% Generate clean signal +f_sig = 500; +clean_sig = sin(2 * PI * f_sig * input); + +% Generate noise signal with a delay of 2 samples +noise = [zeros(2, 1); clean_sig(1:end-2)]; + +% Create noisy signal by adding noise to clean signal +noisy_sig = clean_sig + noise; + +% LMS filter parameters +mu = 0.01; +filterSize = 32; + +% LMS filter implementation +w = zeros(filterSize, 1); +y = zeros(INPUT_LENGTH, 1); + +for n = filterSize:INPUT_LENGTH + x = noisy_sig(n:-1:n-filterSize+1); + y(n) = w' * x; + e = clean_sig(n) - y(n); + w = w + mu * e * x; +end + +% Print result +fprintf('%f\n', y); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/energyOfSignal.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/energyOfSignal.m new file mode 100644 index 000000000000..787269371ac3 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/energyOfSignal.m @@ -0,0 +1,22 @@ +% Constants +INPUT_LENGTH = 100000000; + +% getRange function +input = getRange(0, INPUT_LENGTH, 1); + +% DFT function (using built-in FFT) +fft_result = fft(input); + +% Square of absolute values +sq_abs = abs(fft_result).^2; + +% Sum and average +res = mean(sq_abs); + +% Display result +fprintf('%f\n', res); + +% getRange function +function output = getRange(start, noOfSamples, increment) + output = start + (0:noOfSamples-1) * increment; +end \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/hearingAid.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/hearingAid.m new file mode 100644 index 000000000000..47208238ddc7 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/hearingAid.m @@ -0,0 +1,45 @@ +% Define constants +INPUT_LENGTH = 100000000; +fs = 8000; +step = 1 / fs; + +% Generate input range +input = (0:step:(INPUT_LENGTH-1)*step)'; + +% Generate clean signal +f_sig = 500; +clean_sig = sin(2 * pi * f_sig * input); + +% Generate noise signal with frequency of 3000 Hz +f_noise = 3000; +noise = 0.5 * sin(2 * pi * f_noise * input); + +% Create noisy signal by adding noise to clean signal +noisy_sig = clean_sig + noise; + +% LMS filter response function +function y = lmsFilterResponse(noisy_sig, clean_sig, mu, filterSize) + w = zeros(filterSize, 1); + y = zeros(size(noisy_sig)); + + for n = 1:length(noisy_sig) + x = noisy_sig(max(1, n-filterSize+1):n); + x = [zeros(filterSize - length(x), 1); x]; + y(n) = w' * x; + e = clean_sig(n) - y(n); + w = w + mu * e * x; + y(n) = e; + end +end + +% Apply LMS filter +mu = 0.01; +filterSize = 32; +y = lmsFilterResponse(noisy_sig, clean_sig, mu, filterSize); + +% Apply final gain factor G1 to the LMS filter output +G1 = 1002300; +sol = G1 * y; + +% Display +disp(sol); \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/lowPassFIRFilterDesign.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/lowPassFIRFilterDesign.m new file mode 100644 index 000000000000..0b364f0d3e89 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/lowPassFIRFilterDesign.m @@ -0,0 +1,48 @@ +% Constants +INPUT_LENGTH = 100000000; +FS = 8000; +FC1 = 500; +FC2 = 600; +FC3 = 1000; + +% Calculate normalized frequencies +wc1 = 2 * pi * FC1 / FS; +wc2 = 2 * pi * FC2 / FS; +wc3 = 2 * pi * FC3 / FS; + +% Create Hamming window +hamming_window = hamming(INPUT_LENGTH); + +% Create high-pass filters +hpf1 = highPassFIRFilter(wc1, INPUT_LENGTH); +hpf2 = highPassFIRFilter(wc2, INPUT_LENGTH); +hpf3 = highPassFIRFilter(wc3, INPUT_LENGTH); + +% Element-wise multiplication with Hamming window +hpf_w1 = hpf1 .* hamming_window'; +hpf_w2 = hpf2 .* hamming_window'; +hpf_w3 = hpf3 .* hamming_window'; + +% Get specific elements +final1 = hpf_w1(7); +final2 = hpf_w2(8); +final3 = hpf_w3(9); + +% Display results +fprintf('%f\n', final1); +fprintf('%f\n', final2); +fprintf('%f\n', final3); + +% High-pass FIR filter function +function h = highPassFIRFilter(wc, filterLength) + n = 0:(filterLength-1); + mid = (filterLength-1) / 2; + h = zeros(1, filterLength); + + % Use logical indexing to avoid issues with non-integer indices + midIndex = (n ~= mid); + h(midIndex) = -sin(wc * (n(midIndex) - mid)) ./ (pi * (n(midIndex) - mid)); + + % Handle the middle point separately + h(floor(mid)+1) = 1 - (wc / pi); +end \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/lowPassFull.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/lowPassFull.m new file mode 100644 index 000000000000..4b5348e6641f --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/lowPassFull.m @@ -0,0 +1,40 @@ +% Define constants +PI = pi; +INPUT_LENGTH = 100000000; +fs = 8000; + +% Generate input vector +input = (0:0.000125:(INPUT_LENGTH-1)*0.000125)'; + +% Signal processing steps +f_sig = 500; +getSinDuration = 2 * PI * f_sig * input; +clean_sig = sin(getSinDuration); + +f_noise = 3000; +getNoiseSinDuration = 2 * PI * f_noise * input; +noise = sin(getNoiseSinDuration); + +scaled_noise = 0.5 * noise; +noisy_sig = clean_sig + scaled_noise; + +% Filter design +fc = 1000; +wc = 2 * PI * fc / fs; +N = 101; + +% Low-pass FIR filter +n = -(N-1)/2:(N-1)/2; +lpf = (wc / PI) * sinc(wc * n / PI); + +% Hamming window +hamming = 0.54 - 0.46 * cos(2 * PI * (0:N-1) / (N-1)); + +% Apply window to filter +lpf_w = lpf .* hamming; + +% Apply FIR filter +FIRfilterResponse = filter(lpf_w, 1, noisy_sig); + +% Display results +disp(FIRfilterResponse(2)); \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/noiseCancelling.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/noiseCancelling.m new file mode 100644 index 000000000000..be40a7369d23 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/noiseCancelling.m @@ -0,0 +1,32 @@ +% Constants +INPUT_LENGTH = 100000000; + +% Main script +t = linspace(0, INPUT_LENGTH * 0.000125, INPUT_LENGTH); + +f_sig = 500; +clean_sig = sin(2 * pi * f_sig * t); + +f_noise = 3000; +noise = 0.5 * sin(2 * pi * f_noise * t); + +noisy_sig = clean_sig + noise; + +% LMS filter response +mu = 0.01; +filterSize = 32; + +% Preallocate arrays +w = zeros(1, filterSize); +y = zeros(1, INPUT_LENGTH); + +% Implement LMS filter +for n = filterSize:INPUT_LENGTH + x = noisy_sig(n:-1:n-filterSize+1); + y(n) = w * x'; + e = clean_sig(n) - y(n); + w = w + mu * e * x; +end + +sol = 10 * y; +fprintf('%f\n', sol); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/periodogram.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/periodogram.m new file mode 100644 index 000000000000..8484900de377 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/periodogram.m @@ -0,0 +1,20 @@ +% Define INPUT_LENGTH globally +INPUT_LENGTH = 10; + +% Generate input range +input = 0:1:(INPUT_LENGTH-1); + +% Reverse input +reverse_input = flip(input); + +% FIR Filter Response (Convolution) +conv1d = conv(input, reverse_input, 'same'); + +% Compute DFT using FFT +fft_result = fft(conv1d); + +% Compute square magnitude +sq = abs(fft_result).^2; + +% Display results +fprintf('%f\n', sq); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/underWaterCommunication.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/underWaterCommunication.m new file mode 100644 index 000000000000..bd4e3f04fe17 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/underWaterCommunication.m @@ -0,0 +1,79 @@ +% Constants +INPUT_LENGTH = 100000000; +FILTER_ORDER = 5; + +% Sampling frequency +fs = 1000; + +% Generate input vector +input = getRangeOfVector(0, INPUT_LENGTH, 1); + +% Gain calculation +getMultiplier = 2 * pi * 5; +getSinDuration = gain(input, getMultiplier); + +% Sine wave generation +signal = sine(getSinDuration); + +% Adding delay (noise) +noise = delay(signal, 5); + +% Adding signal and noise +noisy_sig = add(signal, noise); + +% Low-pass filter parameters +fc = 1000; +wc = 2 * pi * fc / 500; % wc should vary from 0 to pi + +% Low-pass FIR filter design +lpf = lowPassFIRFilter(wc, FILTER_ORDER); +hamming_window = hamming(FILTER_ORDER); + +% Apply Hamming window to the filter +lpf_w = lpf .* hamming_window; + +% FIR filter response +FIRfilterResponse = FIRFilterResponse(noisy_sig, lpf_w); + +% Thresholding operation +threshold = 0.5; +GetThresholdReal = thresholdUp(FIRfilterResponse, threshold, 0); + +% Display the result +disp(GetThresholdReal(3)); + +% Function implementations + +function vector = getRangeOfVector(start, length, increment) + vector = (start : increment : start + (length-1)*increment)'; +end + +function output = gain(input, multiplier) + output = input * multiplier; +end + +function output = sine(input) + output = sin(input); +end + +function output = delay(input, delaySamples) + output = [zeros(delaySamples, 1); input(1:end-delaySamples)]; +end + +function output = add(input1, input2) + output = input1 + input2; +end + +function filter = lowPassFIRFilter(wc, length) + n = (-(length-1)/2:(length-1)/2)'; + filter = wc/pi * sinc(wc/pi * n); +end + +function output = FIRFilterResponse(input, filter) + output = conv(input, filter, 'same'); +end + +function output = thresholdUp(input, threshold, defaultValue) + output = max(input, threshold); + output(output == threshold) = defaultValue; +end \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/vibrationAnalysis.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/vibrationAnalysis.m new file mode 100644 index 000000000000..934c4e9501a2 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/vibrationAnalysis.m @@ -0,0 +1,39 @@ +% Constants +INPUT_LENGTH = 10000000; +fs = 1000; + +% Generate input signal +input = 0:(INPUT_LENGTH-1); + +% Generate first sinusoidal signal +getMultiplier = 2 * pi * 50; +getSinDuration = input * getMultiplier; +sig1 = sin(getSinDuration); + +% Generate second sinusoidal signal +getMultiplier2 = 2 * pi * 120; +getSinDuration2 = input * getMultiplier2; +sig2 = 0.5 * sin(getSinDuration2); + +% Combine signals +signal = sig1 + sig2; + +% Add delayed noise +noise = [zeros(1, 5), signal(1:end-5)]; +noisy_sig = signal + noise; + +% Perform DFT +dft_output = fft(noisy_sig); + +% Calculate squared magnitude +sq_abs = abs(dft_output).^2; + +% Calculate mean +res = mean(sq_abs); + +% Apply threshold +threshold_value = 0.2; +GetThresholdReal = sq_abs .* (sq_abs >= threshold_value); + +% Display results +disp(GetThresholdReal); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/voiceActivityDetection.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/voiceActivityDetection.m new file mode 100644 index 000000000000..a3cc47b620c8 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/voiceActivityDetection.m @@ -0,0 +1,57 @@ +% Constants +INPUT_LENGTH = 100000000; + +% Main script +fs = 1000; +input = getRangeOfVector(0, INPUT_LENGTH, 1); + +getMultiplier = 2 * pi * 5; +getSinDuration = gain(input, getMultiplier); + +signal = sine(getSinDuration); + +noise = delay(signal, 5); + +noisy_sig = add(signal, noise); + +threshold_value = 0.8; +GetThresholdReal = threshold(noisy_sig, threshold_value); + +zcr = zeroCrossCount(GetThresholdReal); + +% Display results +disp(GetThresholdReal(4)); + +% Print zero-crossing count +fprintf('Zero-crossing count: %d\n', zcr); + +% Function implementations +function vector = getRangeOfVector(start, length, increment) + vector = (start : increment : start + (length-1)*increment)'; +end + +function output = gain(input, multiplier) + output = input * multiplier; +end + +function output = sine(input) + output = sin(input); +end + +function output = delay(input, delaySamples) + output = [zeros(delaySamples, 1); input(1:end-delaySamples)]; +end + +function output = add(input1, input2) + output = input1 + input2; +end + +function output = threshold(input, thresholdValue) + output = input; + output(abs(input) < thresholdValue) = 0; +end + +function count = zeroCrossCount(input) + signs = sign(input); + count = sum(abs(diff(signs)) == 2); +end \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/periodogram2Conv.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/periodogram2Conv.py deleted file mode 100644 index f19baf5fa17d..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/periodogram2Conv.py +++ /dev/null @@ -1,51 +0,0 @@ - -def main() { - - #Steps: - #calculate x[l] , x[-l] - #calculate conv1d of x[l] , x[-l] ie, conv1 = conv(x[l] , x[-l]) - #calculate fft : res = fft(conv1) - #then periodogram = |abs(fft)|^2 = real^2 + img^2 - - #Another way: - #pad x[l] & x[-l] with zeroes - #calculate fft of x[l] & x[-l] ie, fft_x , fft_reverse_x - #multiply them to get final real ans : fft_x * fft_reverse_x - - #size 10 - # var a10 = [ 10,20,30,40,50,60,70,80,90,100]; - var input = getRangeOfVector(0, 10, 1); - # var input = [1,2,3,4]; - # print(a10); - - #Get x[-l] ie, reverseInput & - var reverse_input = reverseInput(input); - var conv1d = FIRFilterResponse(input, reverse_input); - # var fft_real = fft1DRealSymm(conv1d); #fft1DRealSymm - var fft_real = fft1dreal(conv1d); - var fft_img = fft1dimg(conv1d); - # var sq = fft_real * fft_real + fft_img * fft_img; - # print(sq); - var final1 = getElemAtIndx(fft_real , [6]); - var final2 = getElemAtIndx(fft_real , [7]); - print(final1); - print(final2); - # print(conv1d); - # print(fft_real); - # print(fft_img); - #Pad the input , reverse_input for the size of conv o/p - #Calculate - # var padLen = 9 ; #10 + 10 - 1 - 10 - # var input_padded = padding(input , 0, padLen ); - - - # var fft10real = fft1dreal(input); - # var fft10img = fft1dimg(input); - - # #try input * -input - # var neg_input = gain(input , -1); - # var sq = fft10real * fft10real + fft10img * fft10img; - # print(sq); - -} - diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/ExtractOpName.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/ExtractOpName.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/ExtractOpName.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/ExtractOpName.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/HammingWindow.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/HammingWindow.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/HammingWindow.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/HammingWindow.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/LMSNoiseFilter.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/LMSNoiseFilter.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/LMSNoiseFilter.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/LMSNoiseFilter.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/FIRFilterHammingOpt.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/FIRFilterHammingOpt.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/FIRFilterHammingOpt.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/FIRFilterHammingOpt.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/ScriptForMlirAffine.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/ScriptForMlirAffine.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/ScriptForMlirAffine.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/ScriptForMlirAffine.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/ScriptForSingleRun.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/ScriptForSingleRun.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/ScriptForSingleRun.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/ScriptForSingleRun.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/audioCompression.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/audioCompression.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/audioCompression.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/audioCompression.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/back2backDelay.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/back2backDelay.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/back2backDelay.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/back2backDelay.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/energyOfSignal.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/energyOfSignal.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/energyOfSignal.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/energyOfSignal.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/firFilter10.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/firFilter10.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/firFilter10.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/firFilter10.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/lowPassFIRFilterDesign1.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/lowPassFIRFilterDesign1.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/lowPassFIRFilterDesign1.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/lowPassFIRFilterDesign1.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/lowPassFull1.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/lowPassFull1.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/lowPassFull1.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/lowPassFull1.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/noisecancelling.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/noisecancelling.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/PyDSL/noisecancelling.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/noisecancelling.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/periodogram2Conv.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/periodogram2Conv.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/periodogram2Conv.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/PyDSL/periodogram2Conv.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Quantization.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/Quantization.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Quantization.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/Quantization.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/TryHearingAid copy.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/TryHearingAid copy.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/TryHearingAid copy.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/TryHearingAid copy.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/TryHearingAid.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/TryHearingAid.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/TryHearingAid.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/TryHearingAid.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/audioEqualizer.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/audioEqualizer.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/audioEqualizer.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/audioEqualizer.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/bandPassfilter.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/bandPassfilter.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/bandPassfilter.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/bandPassfilter.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/filterDesign.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/filterDesign.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/filterDesign.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/filterDesign.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/hearingAid.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/hearingAid.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/hearingAid.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/hearingAid.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/highPassfilter.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/highPassfilter.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/highPassfilter.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/highPassfilter.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/lmsNoiseCancelling.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/lmsNoiseCancelling.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/lmsNoiseCancelling.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/lmsNoiseCancelling.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/lowPassFilterApp.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/lowPassFilterApp.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/lowPassFilterApp.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/lowPassFilterApp.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/periodogramHelp.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/periodogramHelp.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/periodogramHelp.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/periodogramHelp.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/periodogramHelp2.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/periodogramHelp2.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/periodogramHelp2.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/ExamplePythonApps/periodogramHelp2.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/ScriptSteps.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/HelperScripts/ScriptSteps.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/ScriptSteps.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/HelperScripts/ScriptSteps.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/generate_dense_inputs.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/HelperScripts/generate_dense_inputs.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/generate_dense_inputs.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/HelperScripts/generate_dense_inputs.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/matlab_result.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/HelperScripts/matlab_result.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/matlab_result.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/HelperScripts/matlab_result.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/working_slidingwind.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/HelperScripts/working_slidingwind.py similarity index 100% rename from mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/working_slidingwind.py rename to mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/HelperScripts/working_slidingwind.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/.gitignore b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/.gitignore deleted file mode 100644 index 1ee5b456d2cd..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/.gitignore +++ /dev/null @@ -1 +0,0 @@ -periodogram/* \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/.gitignore b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/.gitignore deleted file mode 100644 index 698bdfbad524..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -Output/* -dsp1 -dsp1_Debug \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/EnergyOfSignal.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/EnergyOfSignal.py deleted file mode 100644 index 56d974b5fd8d..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/EnergyOfSignal.py +++ /dev/null @@ -1,40 +0,0 @@ - -def main() { - - #Steps: - #calculate x[l] - #calculate fft : fft1 = fft(conv1) - #then sq_abs = |abs(fft)|^2 = real^2 + img^2 - # sum = sum(sq_abs) - # res = gain(sum , 1/N) - - #Optimized res: - #sq1 = input * input - #sum1 = sum(sq1) - - - #size 10 - # var a10 = [ 10,20,30,40,50,60,70,80,90,100]; - var input = getRangeOfVector(0, 30000, 1); - #calculate x[l] - #calculate fft : fft1 = fft(conv1) - var fft_real = fft1dreal(input); - var fft_img = fft1dimg(input); - - #then sq_abs = |abs(fft)|^2 = real^2 + img^2 - # var sq_abs = fft_real * fft_real + fft_img * fft_img ; - var sq_abs = square(fft_real) + square(fft_img) ; - # sum = sum(sq_abs) - var sum1 = sum(sq_abs); - # res = gain(sum , 1/N) - var len1 = len(input); - var res = sum1 / len1; - - print(res); - var final1 = getElemAtIndx(fft_real , [6]); - print(final1); - - - -} - From 9a556535ec0a1daed340c8eac3094031c6c0df59 Mon Sep 17 00:00:00 2001 From: "Kuo, Mei-Chun" <94007620+Megan0704-1@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:01:46 -0700 Subject: [PATCH 15/45] beam form op (#19) --- .../dsp/SimpleBlocks/include/toy/Ops.td | 22 +++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 52 +++++++ .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 139 +++++++++++++++++- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 18 +++ .../test/Examples/DspExample/dsp_beam_form.py | 9 ++ 5 files changed, 237 insertions(+), 3 deletions(-) create mode 100644 mlir/test/Examples/DspExample/dsp_beam_form.py diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index b25ec770150b..7a5d7983d58f 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -2240,6 +2240,28 @@ def FindPeaksOp : Dsp_Op<"find_peaks", [Pure , DeclareOpInterfaceMethods]> { + let summary = "Dsp dialect Beam forming operation"; + let description = [{ + Performs a beam forming signal encoding on the input tensor using specified weights. + }]; + + let arguments = (ins I64Attr:$antennas, I64Attr:$freq, F64Tensor:$time, F64Tensor:$weights); + + let results = (outs F64Tensor:$output); + + let builders = [ + OpBuilder<(ins "int64_t":$antennas, "int64_t":$freq, "Value":$time, "Value":$weights)> + ]; + + let hasVerifier = 1; +} + #endif // TOY_OPS diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index 18a51871c3ec..420b42424539 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -2907,6 +2907,58 @@ mlir::LogicalResult QamModulateImgOp::verify() { return mlir::success(); } + +//===----------------------------------------------------------------------===// +// BeamFormOp +//===----------------------------------------------------------------------===// + +void BeamFormOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, int64_t antennas, int64_t freq, mlir::Value time, mlir::Value weights) { + state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); + state.addAttribute("antennas", builder.getI64IntegerAttr(antennas)); + state.addAttribute("freq", builder.getI64IntegerAttr(freq)); + state.addOperands({time, weights}); +} + +void BeamFormOp::inferShapes() { getResult().setType(getTime().getType()); } + +mlir::LogicalResult BeamFormOp::verify() { + auto timeType = llvm::dyn_cast(getTime().getType()); + auto weightType = llvm::dyn_cast(getWeights().getType()); + + if(!timeType) { + llvm::errs() << "expect a ranked tensor for time input array."; + return mlir::failure(); + } + if(!weightType){ + llvm::errs() << "expect a ranked tensor for weight input array."; + return mlir::failure(); + } + + auto timeShape = timeType.getShape(); + auto timeRank = timeType.getRank(); + auto weightShape = weightType.getShape(); + auto weightRank = weightType.getRank(); + + if(timeRank != 1) { + llvm::errs() << "expect input time array to be 1 dim.\n"; + return mlir::failure(); + } + if(weightRank != 1) { + llvm::errs() << "expect input weight array to be 2 dim.\n"; + return mlir::failure(); + } + + auto antennas = getAntennas(); + llvm::errs() << "mk type check, antenna value: " << antennas << "\n"; + + auto shape = weightShape[0]; + if(shape != antennas) { + llvm::errs() << "expect weight to have shape: [" << antennas << "]\n"; + return mlir::failure(); + } + return mlir::success(); +} + //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 721a58e8fdef..118a58df46d2 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -31,6 +31,7 @@ #include "toy/Passes.h" #include "mlir/Dialect/Affine/IR/AffineOps.h" +#include "mlir/Dialect/Linalg/IR/Linalg.h" #include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/Math/IR/Math.h" @@ -8022,6 +8023,138 @@ struct QamDemodulateOpLowering : public ConversionPattern { } }; // qam_demodulate op +/===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: BeamForm operations +//===----------------------------------------------------------------------===// + +struct BeamFormOpLowering : public ConversionPattern { + BeamFormOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::BeamFormOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + auto beamFormOp = llvm::dyn_cast(op); + + // allocating space for output + auto output = llvm::dyn_cast((*op->result_type_begin())); + llvm::errs() << "output case.\n"; + auto outputMemRefType = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMemRefType, loc, rewriter); + + BeamFormOpAdaptor beamFormAdaptor(operands); + auto time = beamFormAdaptor.getTime(); + auto weights = beamFormAdaptor.getWeights(); + + // allocating space for internal generated signals + auto timeDim = output.getShape()[0]; //dry run: 9 + int64_t antennas = beamFormOp.getAntennas(); + int64_t frequency = beamFormOp.getFreq(); + + llvm::ArrayRef signalShape{antennas, timeDim}; + auto signalType = output.clone(signalShape); + + auto signalMemRefType = convertTensorToMemRef(signalType); + auto allocSignal = insertAllocAndDealloc(signalMemRefType, loc, rewriter); + llvm::errs() << "signal alloc\n"; + + AffineExpr d0, d1; // i, j for generated signal dimension + bindDims(rewriter.getContext(), d0, d1); + + // generated input map + AffineMap genInputMap = AffineMap::get( + 2 /* dim */, 0 /* sym */, ArrayRef{d0, d1}, rewriter.getContext() + ); + // time affine map + AffineMap timeMap = AffineMap::get( + 2 /* dim */, 0 /* sym */, ArrayRef{d1}, rewriter.getContext() + ); + + // output map + AffineMap outputMap = AffineMap::get( + 2, 0, ArrayRef{d0}, rewriter.getContext() + ); + + auto pi = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.1415926)); + auto two = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(2)); + auto four = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(4)); + auto two_pi = rewriter.create(loc, pi, two); // 2 * pi + auto freq_val = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(frequency)); + auto phase_var = rewriter.create(loc, two_pi, freq_val); + llvm::errs() << "const alloc\n"; + + // for loop from 0 to phase + int64_t lb = 0, ub = antennas, step=1; + affine::AffineForOp forOpI = rewriter.create(loc, lb, ub, step); + auto ivI = forOpI.getInductionVar(); // i : phase + rewriter.setInsertionPointToStart(forOpI.getBody()); + llvm::errs() << "first loop\n"; + + // get the induction var to phase variable + auto intType = rewriter.getI64Type(); + auto intI = rewriter.create(loc, intType, ivI); + auto floatType = rewriter.getF64Type(); + auto floatI = rewriter.create(loc, floatType, intI); + + auto iter_tmp = rewriter.create(loc, floatI, pi); // i * pi + auto iter_args = rewriter.create(loc, iter_tmp, four); + + // for loop from 0 to timeDim + ub = timeDim; + affine::AffineForOp forOpJ = rewriter.create(loc, lb, ub, step); + auto ivJ = forOpJ.getInductionVar(); // i : phase + rewriter.setInsertionPointToStart(forOpJ.getBody()); + llvm::errs() << "second loop\n"; + + // loop body + auto time_var = rewriter.create(loc, time, timeMap, ValueRange{ivI, ivJ}); + auto mul_var = rewriter.create(loc, time_var, phase_var); + auto sin_body = rewriter.create(loc, mul_var, iter_args); + auto result = rewriter.create(loc, sin_body); + llvm::errs() << "body loop\n"; + rewriter.create(loc, result, allocSignal, ValueRange{ivI, ivJ}); + + forOpJ.dump(); + rewriter.setInsertionPointAfter(forOpJ); + rewriter.setInsertionPointAfter(forOpI); + + ub = antennas; + affine::AffineForOp forOpIOut = rewriter.create(loc, lb, ub, step); + auto ivIoutput = forOpIOut.getInductionVar(); + rewriter.setInsertionPointToStart(forOpIOut.getBody()); + llvm::errs() << "beam 1 loop\n"; + + ub = timeDim; + affine::AffineForOp forOpJOut = rewriter.create(loc, lb, ub, step); + auto ivJoutput = forOpJOut.getInductionVar(); + rewriter.setInsertionPointToStart(forOpJOut.getBody()); + llvm::errs() << "beam 2 loop\n"; + + // load from signal input + auto signalInput = rewriter.create(loc, allocSignal, genInputMap, ValueRange{ivIoutput, ivJoutput}); + auto weight = rewriter.create(loc, weights, outputMap, ValueRange{ivIoutput, ivJoutput}); + auto intermediateVal = rewriter.create(loc, signalInput, weight); + llvm::errs() << "load from signal input \n"; + + // load from output + auto outputVal = rewriter.create(loc, alloc, ValueRange{ivJoutput}); + auto beamOut = rewriter.create(loc, intermediateVal, outputVal); + llvm::errs() << "load from bean output\n"; + + rewriter.create(loc, beamOut, alloc, ValueRange{ivJoutput}); + + rewriter.setInsertionPointAfter(forOpJOut); + rewriter.setInsertionPointAfter(forOpIOut); + + rewriter.replaceOp(op, alloc); + + llvm::errs() << "success loop\n"; + return mlir::success(); + + } +}; + } // namespace //===----------------------------------------------------------------------===// @@ -8039,7 +8172,7 @@ struct ToyToAffineLoweringPass void getDependentDialects(DialectRegistry ®istry) const override { registry .insert(); + math::MathDialect, scf::SCFDialect, linalg::LinalgDialect>(); } void runOnOperation() final; }; @@ -8056,7 +8189,7 @@ void ToyToAffineLoweringPass::runOnOperation() { target.addLegalDialect(); + scf::SCFDialect, linalg::LinalgDialect>(); // We also define the Toy dialect as Illegal so that the conversion will fail // if any of these operations are *not* converted. Given that we actually want @@ -8094,7 +8227,7 @@ void ToyToAffineLoweringPass::runOnOperation() { FIRFilterYSymmOptimizedOpLowering, FFT1DRealSymmOpLowering, FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering, ShiftRightOpLowering, MatmulOpLowering, - ThresholdUpOpLowering, QamModulateRealOpLowering, QamModulateImgOpLowering, QamDemodulateOpLowering, FindPeaksOpLowering>(&getContext()); + ThresholdUpOpLowering, QamModulateRealOpLowering, QamModulateImgOpLowering, QamDemodulateOpLowering, FindPeaksOpLowering, BeamFormOpLowering>(&getContext()); // With the target and rewrite patterns defined, we can now attempt the // conversion. The conversion will signal failure if any of our `illegal` diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index e68532eec7f6..ff56ef6d95fa 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -867,7 +867,25 @@ class MLIRGenImpl { return builder.create(location, operands[0], operands[1], operands[2]); } + // beam form + if (callee == "beam_form") { + if (call.getArgs().size() != 4) { + emitError(location, + "MLIR codegen encountered an error: dsp.GenerateDTMFOp " + "accepts 4 argument"); + return nullptr; + } + auto antennaConst = operands[0].getDefiningOp(); + auto freqConst = operands[1].getDefiningOp(); + auto antennaVal = antennaConst.getValue().getValues(); + auto freqVal = freqConst.getValue().getValues(); + + double antenna = antennaVal[0].getValueAsDouble(); + double freq = freqVal[0].getValueAsDouble(); + return builder.create(location, antenna, freq, + operands[2], operands[3]); + } // qam modulate op if(callee == "qam_modulate_real") { if(call.getArgs().size() != 1) { diff --git a/mlir/test/Examples/DspExample/dsp_beam_form.py b/mlir/test/Examples/DspExample/dsp_beam_form.py new file mode 100644 index 000000000000..64150abc810a --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_beam_form.py @@ -0,0 +1,9 @@ +def main() { + var time = [0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.]; + var antennas = 4; + var freq = 5; + var weights = [1,2,3,4]; + + var signal = beam_form(antennas, freq, time, weights); + print(signal); +} From ff6428816ced6fced7cb64ec19ecf927a09574b3 Mon Sep 17 00:00:00 2001 From: "Kuo, Mei-Chun" <94007620+Megan0704-1@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:54:50 -0700 Subject: [PATCH 16/45] qam application (#22) --- .../BenchmarkTest/CCode/digital_modulaion.c | 77 +++++++++++++++++++ .../DSP-DSL/digital_modulation.py | 10 +++ .../BenchmarkTest/Matlab/digital_modulation.m | 71 +++++++++++++++++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 17 ---- .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 4 +- 5 files changed, 161 insertions(+), 18 deletions(-) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/digital_modulaion.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/digital_modulation.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/digital_modulation.m diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/digital_modulaion.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/digital_modulaion.c new file mode 100644 index 000000000000..4f2971ef0e35 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/digital_modulaion.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include + +#define INPUT_LENGTH 8 + +void generate_input(int *data) { + for(int i=0; i(getReal().getType()); auto imagineType = llvm::dyn_cast(getImagine().getType()); - if(!realType) { - llvm::errs() << "expect a ranked tensor for real part array, get " << getReal() << " instead\n"; - return mlir::failure(); - } - if(!imagineType) { - llvm::errs() << "expect a ranked tensor for imagine part array, get " << getImagine() << " instead\n"; - return mlir::failure(); - } - - auto realShape = realType.getShape(); - auto imagineShape = imagineType.getShape(); - - if(realShape.size() != imagineShape.size()) { - llvm::errs() << "expect real array and imagine array to have same tensor shape.\n"; - return mlir::failure(); - } - return mlir::success(); } diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 118a58df46d2..46931c3010a3 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -7893,6 +7893,7 @@ struct QamModulateRealOpLowering : public ConversionPattern { Value out = rewriter.create(loc, zeroReal, negOneVal, oneVal); rewriter.create(loc, out, alloc, outputRealMap, ValueRange{ivI}); + rewriter.setInsertionPointAfter(forOpI); rewriter.replaceOp(op, alloc); return success(); @@ -7946,6 +7947,7 @@ struct QamModulateImgOpLowering : public ConversionPattern { rewriter.create(loc, out, alloc, outputImgMap, ValueRange{ivI}); + rewriter.setInsertionPointAfter(forOpI); rewriter.replaceOp(op, alloc); return success(); @@ -8023,7 +8025,7 @@ struct QamDemodulateOpLowering : public ConversionPattern { } }; // qam_demodulate op -/===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: BeamForm operations //===----------------------------------------------------------------------===// From 31cf03d1e04caa41c2d4adaa6cdb46d1333c6d78 Mon Sep 17 00:00:00 2001 From: Ameya Gurjar <76804848+Ameya674@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:24:15 -0700 Subject: [PATCH 17/45] add dynamic fft and median filter (#21) * add dynamic fft and median filter --- .../dsp/SimpleBlocks/include/toy/Ops.td | 23 +- .../dsp/SimpleBlocks/mlir/Dialect.cpp | 34 + .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 1305 ++++++++++------- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 9 + mlir/test/Examples/DspExample/dsp_fft.py | 2 +- mlir/test/Examples/DspExample/dsp_medfilt.py | 18 + .../DspExample/dsp_signal_smoothing.py | 7 + 7 files changed, 828 insertions(+), 570 deletions(-) create mode 100644 mlir/test/Examples/DspExample/dsp_medfilt.py create mode 100644 mlir/test/Examples/DspExample/dsp_signal_smoothing.py diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 7a5d7983d58f..f8029b615086 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -567,7 +567,9 @@ def SubOp : Dsp_Op<"sub", [Pure , DeclareOpInterfaceMethods]> { let summary = "Performs FFT Operation on the input"; let description = [{ - Takes the input array and returns the real part of the fourier transform, an array of the same size. + This function accepts a 1D input array of size 2^n and returns the real part of its Fourier transform, + producing an output array of the same size. The function is designed to work exclusively with input sizes that are powers of 2. + Providing an array of any other size will result in a segmentation fault. }]; let arguments = (ins F64Tensor:$lhs); @@ -585,7 +587,9 @@ def FFTRealOp : Dsp_Op<"fftReal", [Pure, DeclareOpInterfaceMethods]> { let summary = "Performs FFT Operation on the input"; let description = [{ - Takes the input array and returns the imaginary part of the fourier transform, an array of the same size. + This function accepts a 1D input array of size 2^n and returns the imaginary part of its Fourier transform, + producing an output array of the same size. The function is designed to work exclusively with input sizes that are powers of 2. + Providing an array of any other size will result in a segmentation fault. }]; let arguments = (ins F64Tensor:$lhs); @@ -666,6 +670,21 @@ def FIRFilterResponseOp : Dsp_Op<"FIRFilterResponse" , let hasVerifier = 1; } +//===----------------------------------------------------------------------===// +// MedianFilterOp +//===----------------------------------------------------------------------===// + +def MedianFilterOp : Dsp_Op<"medianFilter", + [Pure, DeclareOpInterfaceMethods]> { + + let arguments = (ins F64Tensor:$input); + let results = (outs F64Tensor); + + let builders = [ + OpBuilder<(ins "Value":$input)> + ]; +} + //===----------------------------------------------------------------------===// // SlidingWindowAvg //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index 6fe54810fe40..d6fcc140ea6c 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -779,6 +779,40 @@ mlir::LogicalResult FIRFilterResponseOp::verify() { // } return mlir::success(); +} + +//===----------------------------------------------------------------------===// +// MedianFilterOp +//===----------------------------------------------------------------------===// + +void MedianFilterOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value value) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands(value); +} + +void MedianFilterOp::inferShapes() { + //for each rank + //Get the shape/size of input + //output size = input_size - 2 + auto inputType = llvm::dyn_cast(getOperand().getType()); + + auto shapeOfInput = inputType.getShape(); + + std::vector shapeForOutput; + + //Iterate for each rank : tensor<1x2x3x2> = rank 4 + for(size_t i=0; i < shapeOfInput.size() ; i++){ + shapeForOutput.push_back(shapeOfInput[i] - 2); + } + + mlir::TensorType outputType = mlir::RankedTensorType::get(shapeForOutput, + getInput().getType().getElementType()); + // getOperand().getType()); + // getOperand().getType().getElementType()); + + getResult().setType(outputType); + } //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 46931c3010a3..bee3f94145d9 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -31,9 +31,9 @@ #include "toy/Passes.h" #include "mlir/Dialect/Affine/IR/AffineOps.h" -#include "mlir/Dialect/Linalg/IR/Linalg.h" #include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/Linalg/IR/Linalg.h" #include "mlir/Dialect/Math/IR/Math.h" #include "mlir/Dialect/MemRef/IR/MemRef.h" #include "mlir/Pass/Pass.h" @@ -1800,35 +1800,21 @@ struct LengthOpLowering : public ConversionPattern { //===----------------------------------------------------------------------===// struct FFTRealOpLowering : public ConversionPattern { - // constructor takes the mlir context and the operation as inputs FFTRealOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::FFTRealOp::getOperationName(), 1, ctx) {} - // matchandrewrite - actual lowering logic of the operation - LogicalResult // return type is logical --> success or failure - // checks if the correct function is passed and rewrites it - // takes in the pointer to the operation, list of operands, and the rewriter - // object const - function doesn't modify the class it belongs to final - - // can't be overridden + LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { - // get location of the operation auto loc = op->getLoc(); - // get the type of the result auto tensorType = llvm::cast((*op->result_type_begin())); - // convert the tensorType to memrefType auto memrefType = convertTensorToMemRef(tensorType); - // alloc memory for temp and dealloc when not required + auto alloc_temp_real = insertAllocAndDealloc(memrefType, loc, rewriter); auto alloc_temp_imag = insertAllocAndDealloc(memrefType, loc, rewriter); - // storing the input in real and 0.0 in imag - - // adaptor to get operands FFTRealOpAdaptor fftRealOpAdaptor(operands); auto input = fftRealOpAdaptor.getLhs(); - - // bounds of the affine loop auto lb = rewriter.create(loc, 0); auto ub = rewriter.create(loc, tensorType.getShape()[0]); @@ -1852,43 +1838,80 @@ struct FFTRealOpLowering : public ConversionPattern { auto alloc_reversed_real = insertAllocAndDealloc(memrefType, loc, rewriter); auto alloc_reversed_imag = insertAllocAndDealloc(memrefType, loc, rewriter); - // bit reversal constants - auto constant1 = - rewriter.create(loc, rewriter.getI64IntegerAttr(1)); - auto constant2 = - rewriter.create(loc, rewriter.getI64IntegerAttr(2)); - - // Bit reversal loop - auto bitReversal = rewriter.create(loc, lb, ub, step); - rewriter.setInsertionPointToStart(bitReversal.getBody()); - auto i = bitReversal.getInductionVar(); - // Convert index to i64 - auto i_val = - rewriter.create(loc, rewriter.getI64Type(), i); - // Bit reversal logic - auto bit0 = rewriter.create(loc, i_val, constant1); - auto i_val_shr1 = rewriter.create(loc, i_val, constant1); - auto bit1 = rewriter.create(loc, i_val_shr1, constant1); - auto i_val_shr2 = rewriter.create(loc, i_val, constant2); - auto bit2 = rewriter.create(loc, i_val_shr2, constant1); - auto rev_bit0 = rewriter.create(loc, bit0, constant2); - auto rev_bit1 = rewriter.create(loc, bit1, constant1); - auto rev_temp = rewriter.create(loc, rev_bit0, rev_bit1); - auto rev = rewriter.create(loc, rev_temp, bit2); - // Convert back to index - auto reversed_i = - rewriter.create(loc, rewriter.getIndexType(), rev); - // Load values from temp arrays - auto real_val = + // bits needed for bit reversal + auto ubInt = + rewriter.create(loc, rewriter.getI64Type(), ub); + auto ubFloat = + rewriter.create(loc, rewriter.getF64Type(), ubInt); + auto bitsNeededFloat = rewriter.create(loc, ubFloat); + auto bitsNeededInt = rewriter.create( + loc, rewriter.getI64Type(), bitsNeededFloat); + auto bitsNeeded = rewriter.create( + loc, rewriter.getIndexType(), bitsNeededInt); + + // bit reversal + auto bitReversalLoop = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(bitReversalLoop.getBody()); + auto i = bitReversalLoop.getInductionVar(); + auto iInt = rewriter.create(loc, rewriter.getI64Type(), + i); // check here + + // Calculate reversed index + // auto zero = rewriter.create(loc, 0); + auto initialRevIndex = rewriter.create(loc, 0, 64); + + auto innerLoop = rewriter.create(loc, lb, bitsNeeded, step, + ValueRange{initialRevIndex}); + rewriter.setInsertionPointToStart(innerLoop.getBody()); + auto j = innerLoop.getInductionVar(); + auto jInt = + rewriter.create(loc, rewriter.getI64Type(), j); + auto carriedRevIndex = innerLoop.getRegionIterArgs()[0]; + + auto bitMask = rewriter.create( + loc, rewriter.create(loc, 1, 64), jInt); + auto iAndMask = rewriter.create(loc, iInt, bitMask); + auto isNonZero = rewriter.create( + loc, arith::CmpIPredicate::ne, iAndMask, + rewriter.create(loc, 0, 64)); + auto shiftAmount = rewriter.create( + loc, rewriter.create(loc, bitsNeeded, j), + rewriter.create(loc, 1)); + auto shiftAmountI64 = rewriter.create( + loc, rewriter.getI64Type(), shiftAmount); + auto bitToSet = rewriter.create( + loc, rewriter.create(loc, 1, 64), shiftAmountI64); + + // Update newRevIndex using a select operation + auto updatedRevIndex = rewriter.create( + loc, carriedRevIndex, + rewriter.create( + loc, isNonZero, bitToSet, + rewriter.create(loc, 0, 64))); + + // Yield the updated value to carry it forward + rewriter.create(loc, ValueRange{updatedRevIndex}); + + // auto revIndex = rewriter.create(loc, + // rewriter.getIndexType(), newRevIndex); + + rewriter.setInsertionPointAfter(innerLoop); + + auto finalRevIndex = innerLoop.getResult(0); + auto revIndex = rewriter.create( + loc, rewriter.getIndexType(), finalRevIndex); + + // Load from alloc_temp and store in alloc_reversed + auto realValue = rewriter.create(loc, alloc_temp_real, ValueRange{i}); - auto imag_val = + auto imagValue = rewriter.create(loc, alloc_temp_imag, ValueRange{i}); - // Store values in reversed arrays - rewriter.create(loc, real_val, alloc_reversed_real, - ValueRange{reversed_i}); - rewriter.create(loc, imag_val, alloc_reversed_imag, - ValueRange{reversed_i}); - rewriter.setInsertionPointAfter(bitReversal); + rewriter.create(loc, realValue, alloc_reversed_real, + ValueRange{revIndex}); + rewriter.create(loc, imagValue, alloc_reversed_imag, + ValueRange{revIndex}); + + rewriter.setInsertionPointAfter(bitReversalLoop); // Cooley-Tukey FFT implementation auto N = tensorType.getShape()[0]; @@ -1913,24 +1936,24 @@ struct FFTRealOpLowering : public ConversionPattern { rewriter.setInsertionPointToStart(outerLoop.getBody()); auto start = outerLoop.getInductionVar(); - auto innerLoop = rewriter.create(loc, lb, half_size, step); - rewriter.setInsertionPointToStart(innerLoop.getBody()); - auto j = innerLoop.getInductionVar(); + auto butterflyLoop = rewriter.create(loc, lb, half_size, step); + rewriter.setInsertionPointToStart(butterflyLoop.getBody()); + auto k = butterflyLoop.getInductionVar(); // Calculate indices for even and odd elements - auto even_index = rewriter.create(loc, start, j); + auto even_index = rewriter.create(loc, start, k); auto odd_index = rewriter.create(loc, even_index, half_size); // Calculate twiddle factor - auto j_i64 = - rewriter.create(loc, rewriter.getI64Type(), j); - auto j_f64 = - rewriter.create(loc, rewriter.getF64Type(), j_i64); + auto k_i64 = + rewriter.create(loc, rewriter.getI64Type(), k); + auto k_f64 = + rewriter.create(loc, rewriter.getF64Type(), k_i64); auto full_size_i64 = rewriter.create( loc, rewriter.getI64Type(), full_size); auto full_size_f64 = rewriter.create( loc, rewriter.getF64Type(), full_size_i64); - auto angle_div = rewriter.create(loc, j_f64, full_size_f64); + auto angle_div = rewriter.create(loc, k_f64, full_size_f64); auto angle_mul = rewriter.create(loc, neg2, angle_div); auto angle_final = rewriter.create(loc, pi, angle_mul); auto cos = rewriter.create(loc, angle_final); @@ -1958,7 +1981,6 @@ struct FFTRealOpLowering : public ConversionPattern { ValueRange{even_index}); auto even_imag = rewriter.create(loc, alloc_reversed_imag, ValueRange{even_index}); - // Butterfly operation auto new_even_real = rewriter.create(loc, even_real, t_real); auto new_even_imag = rewriter.create(loc, even_imag, t_imag); @@ -1980,7 +2002,6 @@ struct FFTRealOpLowering : public ConversionPattern { return success(); } }; - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: FFTImagOp operations //===----------------------------------------------------------------------===// @@ -1990,31 +2011,18 @@ struct FFTImagOpLowering : public ConversionPattern { FFTImagOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::FFTImagOp::getOperationName(), 1, ctx) {} - // matchandrewrite - actual lowering logic of the operation - LogicalResult // return type is logical --> success or failure - // checks if the correct function is passed and rewrites it - // takes in the pointer to the operation, list of operands, and the rewriter - // object const - function doesn't modify the class it belongs to final - - // can't be overridden + LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { - // get location of the operation auto loc = op->getLoc(); - // get the type of the result auto tensorType = llvm::cast((*op->result_type_begin())); - // convert the tensorType to memrefType auto memrefType = convertTensorToMemRef(tensorType); - // alloc memory for temp and dealloc when not required + auto alloc_temp_real = insertAllocAndDealloc(memrefType, loc, rewriter); auto alloc_temp_imag = insertAllocAndDealloc(memrefType, loc, rewriter); - // storing the input in real and 0.0 in imag - - // adaptor to get operands - FFTImagOpAdaptor fftImagOpAdaptor(operands); - auto input = fftImagOpAdaptor.getLhs(); - - // bounds of the affine loop + FFTRealOpAdaptor fftRealOpAdaptor(operands); + auto input = fftRealOpAdaptor.getLhs(); auto lb = rewriter.create(loc, 0); auto ub = rewriter.create(loc, tensorType.getShape()[0]); @@ -2038,43 +2046,80 @@ struct FFTImagOpLowering : public ConversionPattern { auto alloc_reversed_real = insertAllocAndDealloc(memrefType, loc, rewriter); auto alloc_reversed_imag = insertAllocAndDealloc(memrefType, loc, rewriter); - // bit reversal constants - auto constant1 = - rewriter.create(loc, rewriter.getI64IntegerAttr(1)); - auto constant2 = - rewriter.create(loc, rewriter.getI64IntegerAttr(2)); - - // Bit reversal loop - auto bitReversal = rewriter.create(loc, lb, ub, step); - rewriter.setInsertionPointToStart(bitReversal.getBody()); - auto i = bitReversal.getInductionVar(); - // Convert index to i64 - auto i_val = - rewriter.create(loc, rewriter.getI64Type(), i); - // Bit reversal logic - auto bit0 = rewriter.create(loc, i_val, constant1); - auto i_val_shr1 = rewriter.create(loc, i_val, constant1); - auto bit1 = rewriter.create(loc, i_val_shr1, constant1); - auto i_val_shr2 = rewriter.create(loc, i_val, constant2); - auto bit2 = rewriter.create(loc, i_val_shr2, constant1); - auto rev_bit0 = rewriter.create(loc, bit0, constant2); - auto rev_bit1 = rewriter.create(loc, bit1, constant1); - auto rev_temp = rewriter.create(loc, rev_bit0, rev_bit1); - auto rev = rewriter.create(loc, rev_temp, bit2); - // Convert back to index - auto reversed_i = - rewriter.create(loc, rewriter.getIndexType(), rev); - // Load values from temp arrays - auto real_val = + // bits needed for bit reversal + auto ubInt = + rewriter.create(loc, rewriter.getI64Type(), ub); + auto ubFloat = + rewriter.create(loc, rewriter.getF64Type(), ubInt); + auto bitsNeededFloat = rewriter.create(loc, ubFloat); + auto bitsNeededInt = rewriter.create( + loc, rewriter.getI64Type(), bitsNeededFloat); + auto bitsNeeded = rewriter.create( + loc, rewriter.getIndexType(), bitsNeededInt); + + // bit reversal + auto bitReversalLoop = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(bitReversalLoop.getBody()); + auto i = bitReversalLoop.getInductionVar(); + auto iInt = rewriter.create(loc, rewriter.getI64Type(), + i); // check here + + // Calculate reversed index + // auto zero = rewriter.create(loc, 0); + auto initialRevIndex = rewriter.create(loc, 0, 64); + + auto innerLoop = rewriter.create(loc, lb, bitsNeeded, step, + ValueRange{initialRevIndex}); + rewriter.setInsertionPointToStart(innerLoop.getBody()); + auto j = innerLoop.getInductionVar(); + auto jInt = + rewriter.create(loc, rewriter.getI64Type(), j); + auto carriedRevIndex = innerLoop.getRegionIterArgs()[0]; + + auto bitMask = rewriter.create( + loc, rewriter.create(loc, 1, 64), jInt); + auto iAndMask = rewriter.create(loc, iInt, bitMask); + auto isNonZero = rewriter.create( + loc, arith::CmpIPredicate::ne, iAndMask, + rewriter.create(loc, 0, 64)); + auto shiftAmount = rewriter.create( + loc, rewriter.create(loc, bitsNeeded, j), + rewriter.create(loc, 1)); + auto shiftAmountI64 = rewriter.create( + loc, rewriter.getI64Type(), shiftAmount); + auto bitToSet = rewriter.create( + loc, rewriter.create(loc, 1, 64), shiftAmountI64); + + // Update newRevIndex using a select operation + auto updatedRevIndex = rewriter.create( + loc, carriedRevIndex, + rewriter.create( + loc, isNonZero, bitToSet, + rewriter.create(loc, 0, 64))); + + // Yield the updated value to carry it forward + rewriter.create(loc, ValueRange{updatedRevIndex}); + + // auto revIndex = rewriter.create(loc, + // rewriter.getIndexType(), newRevIndex); + + rewriter.setInsertionPointAfter(innerLoop); + + auto finalRevIndex = innerLoop.getResult(0); + auto revIndex = rewriter.create( + loc, rewriter.getIndexType(), finalRevIndex); + + // Load from alloc_temp and store in alloc_reversed + auto realValue = rewriter.create(loc, alloc_temp_real, ValueRange{i}); - auto imag_val = + auto imagValue = rewriter.create(loc, alloc_temp_imag, ValueRange{i}); - // Store values in reversed arrays - rewriter.create(loc, real_val, alloc_reversed_real, - ValueRange{reversed_i}); - rewriter.create(loc, imag_val, alloc_reversed_imag, - ValueRange{reversed_i}); - rewriter.setInsertionPointAfter(bitReversal); + rewriter.create(loc, realValue, alloc_reversed_real, + ValueRange{revIndex}); + rewriter.create(loc, imagValue, alloc_reversed_imag, + ValueRange{revIndex}); + + rewriter.setInsertionPointAfter(bitReversalLoop); // Cooley-Tukey FFT implementation auto N = tensorType.getShape()[0]; @@ -2099,24 +2144,24 @@ struct FFTImagOpLowering : public ConversionPattern { rewriter.setInsertionPointToStart(outerLoop.getBody()); auto start = outerLoop.getInductionVar(); - auto innerLoop = rewriter.create(loc, lb, half_size, step); - rewriter.setInsertionPointToStart(innerLoop.getBody()); - auto j = innerLoop.getInductionVar(); + auto butterflyLoop = rewriter.create(loc, lb, half_size, step); + rewriter.setInsertionPointToStart(butterflyLoop.getBody()); + auto k = butterflyLoop.getInductionVar(); // Calculate indices for even and odd elements - auto even_index = rewriter.create(loc, start, j); + auto even_index = rewriter.create(loc, start, k); auto odd_index = rewriter.create(loc, even_index, half_size); // Calculate twiddle factor - auto j_i64 = - rewriter.create(loc, rewriter.getI64Type(), j); - auto j_f64 = - rewriter.create(loc, rewriter.getF64Type(), j_i64); + auto k_i64 = + rewriter.create(loc, rewriter.getI64Type(), k); + auto k_f64 = + rewriter.create(loc, rewriter.getF64Type(), k_i64); auto full_size_i64 = rewriter.create( loc, rewriter.getI64Type(), full_size); auto full_size_f64 = rewriter.create( loc, rewriter.getF64Type(), full_size_i64); - auto angle_div = rewriter.create(loc, j_f64, full_size_f64); + auto angle_div = rewriter.create(loc, k_f64, full_size_f64); auto angle_mul = rewriter.create(loc, neg2, angle_div); auto angle_final = rewriter.create(loc, pi, angle_mul); auto cos = rewriter.create(loc, angle_final); @@ -2144,7 +2189,6 @@ struct FFTImagOpLowering : public ConversionPattern { ValueRange{even_index}); auto even_imag = rewriter.create(loc, alloc_reversed_imag, ValueRange{even_index}); - // Butterfly operation auto new_even_real = rewriter.create(loc, even_real, t_real); auto new_even_imag = rewriter.create(loc, even_imag, t_imag); @@ -6316,6 +6360,74 @@ struct DownSamplingOpLowering : public ConversionPattern { } }; +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: MedianFilterOp operations +//===----------------------------------------------------------------------===// + +struct MedianFilterOpLowering : public ConversionPattern { + MedianFilterOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::MedianFilterOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + auto tensorType = llvm::cast((*op->result_type_begin())); + auto memRefType = convertTensorToMemRef(tensorType); + auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); + + SmallVector lowerBounds(tensorType.getRank(), 0); + SmallVector steps(tensorType.getRank(), 1); + + // For loop + int64_t lb = 0; + int64_t ub = tensorType.getShape()[0]; + int64_t step = 1; + + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, ub, step); + auto iv = forOp1.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp1.getBody()); + MedianFilterOpAdaptor medianFilterOpAdaptor(operands); + + Value elem1 = rewriter.create( + loc, medianFilterOpAdaptor.getInput(), iv); + AffineExpr ExprForElem2 = + rewriter.getAffineDimExpr(0) + rewriter.getAffineConstantExpr(1); + AffineExpr ExprForElem3 = + rewriter.getAffineDimExpr(0) + rewriter.getAffineConstantExpr(2); + AffineMap addMapForElem2 = AffineMap::get(1, 0, ExprForElem2); + AffineMap addMapForElem3 = AffineMap::get(1, 0, ExprForElem3); + Value elem2 = rewriter.create( + loc, medianFilterOpAdaptor.getInput(), addMapForElem2, ValueRange{iv}); + Value elem3 = rewriter.create( + loc, medianFilterOpAdaptor.getInput(), addMapForElem3, ValueRange{iv}); + + // sum + Value sum1 = rewriter.create(loc, elem1, elem2); + Value sum = rewriter.create(loc, sum1, elem3); + + // min + Value minElem1Elem2 = rewriter.create(loc, elem1, elem2); + Value min = rewriter.create(loc, minElem1Elem2, elem3); + + // max + Value maxElem1Elem2 = rewriter.create(loc, elem1, elem2); + Value max = rewriter.create(loc, maxElem1Elem2, elem3); + + // median + Value min_plus_max = rewriter.create(loc, min, max); + Value median = rewriter.create(loc, sum, min_plus_max); + + // store in alloc + rewriter.create(loc, median, alloc, iv); + rewriter.setInsertionPointAfter(forOp1); + rewriter.replaceOp(op, alloc); + return success(); + } +}; + //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: SlidingWindowAvg operations //===----------------------------------------------------------------------===// @@ -7127,104 +7239,106 @@ LoweredOp); } }; - - - //===----------------------------------------------------------------------===// // ToyToAffine AdditionalPatterns: Find peaks operations //===----------------------------------------------------------------------===// -//template +// template struct FindPeaksOpLowering : public ConversionPattern { - FindPeaksOpLowering(MLIRContext *ctx) + FindPeaksOpLowering(MLIRContext *ctx) : ConversionPattern(dsp::FindPeaksOp::getOperationName(), 1, ctx) {} - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { - - //Get the location of GainOp + + // Get the location of GainOp auto loc = op->getLoc(); - - // output for result type - auto tensorType = llvm::cast((*op->result_type_begin())); + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); // allocation & deallocation for the result of this operation auto memRefType = convertTensorToMemRef(tensorType); auto alloc_output = insertAllocAndDealloc(memRefType, loc, rewriter); - - auto countMemRefType = MemRefType::get({}, rewriter.getIndexType()); - auto alloc_peaks_count = insertAllocAndDealloc(countMemRefType, loc, rewriter); - + + auto countMemRefType = MemRefType::get({}, rewriter.getIndexType()); + auto alloc_peaks_count = + insertAllocAndDealloc(countMemRefType, loc, rewriter); + typename dsp::FindPeaksOp::Adaptor findPeaksOpAdaptor(operands); - + Value constant_minus_one = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); - - Value constant_minus_one = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); - - Value constant_index_zero = rewriter.create(loc, rewriter.getIndexType(), rewriter.getIndexAttr(0)); - Value constant_index_one = rewriter.create(loc, rewriter.getIndexType(), rewriter.getIndexAttr(1)); - - rewriter.create(loc, constant_index_zero, alloc_peaks_count, ValueRange{}); + Value constant_index_zero = rewriter.create( + loc, rewriter.getIndexType(), rewriter.getIndexAttr(0)); + Value constant_index_one = rewriter.create( + loc, rewriter.getIndexType(), rewriter.getIndexAttr(1)); + rewriter.create(loc, constant_index_zero, alloc_peaks_count, + ValueRange{}); - - auto signalType = llvm::dyn_cast(op->getOperand(0).getType()); - int64_t lb = 1 ; - int64_t ub = signalType.getShape()[0]-1; + auto signalType = + llvm::dyn_cast(op->getOperand(0).getType()); + int64_t lb = 1; + int64_t ub = signalType.getShape()[0] - 1; int64_t step = 1; - Value constant_len_singal = rewriter.create(loc, rewriter.getIndexType(), rewriter.getIndexAttr(signalType.getShape()[0])); - - //%distance = affine.load %alloc_distance[] : memref - auto distance_fp = rewriter.create( - loc, findPeaksOpAdaptor.getDistance(), ValueRange{}); - //f64 to index - Value distance_ui = - rewriter.create(loc, rewriter.getIntegerType(32), distance_fp); - Value distance = rewriter.create( + Value constant_len_singal = rewriter.create( + loc, rewriter.getIndexType(), + rewriter.getIndexAttr(signalType.getShape()[0])); + + //%distance = affine.load %alloc_distance[] : memref + auto distance_fp = rewriter.create( + loc, findPeaksOpAdaptor.getDistance(), ValueRange{}); + // f64 to index + Value distance_ui = rewriter.create( + loc, rewriter.getIntegerType(32), distance_fp); + Value distance = rewriter.create( loc, rewriter.getIndexType(), distance_ui); - - affine::AffineForOp forOpInit = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOpInit = + rewriter.create(loc, lb, ub, step); auto init_iter = forOpInit.getInductionVar(); rewriter.setInsertionPointToStart(forOpInit.getBody()); - rewriter.create(loc, constant_minus_one, alloc_output, ValueRange{init_iter}); + rewriter.create(loc, constant_minus_one, alloc_output, + ValueRange{init_iter}); rewriter.setInsertionPointAfter(forOpInit); - - - affine::AffineForOp forOpSignal = rewriter.create(loc, lb, ub, step); + affine::AffineForOp forOpSignal = + rewriter.create(loc, lb, ub, step); auto current_index = forOpSignal.getInductionVar(); rewriter.setInsertionPointToStart(forOpSignal.getBody()); // %prev_index = arith.subi %current_index, %cst_one_index : index // %signal_prev = memref.load %alloc_signal[%prev_index] : memref<10xf64> - // %signal_current = affine.load %alloc_signal[%current_index] : memref<10xf64> - // %signal_next = affine.load %alloc_signal[%current_index+1] : memref<10xf64> Q. How can I do this? - // %height = affine.load %alloc_height[] : memref - + // %signal_current = affine.load %alloc_signal[%current_index] : + // memref<10xf64> %signal_next = affine.load %alloc_signal[%current_index+1] + // : memref<10xf64> Q. How can I do this? %height = affine.load + // %alloc_height[] : memref + AffineExpr ExprForPrev = rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(1); AffineMap addMapForPrev = AffineMap::get(1, 0, ExprForPrev); - + AffineExpr ExprForNext = rewriter.getAffineDimExpr(0) + rewriter.getAffineConstantExpr(1); AffineMap addMapForNext = AffineMap::get(1, 0, ExprForNext); - auto signal_prev = rewriter.create( - loc, findPeaksOpAdaptor.getSignal(), addMapForPrev, ValueRange{current_index}); + auto signal_prev = + rewriter.create(loc, findPeaksOpAdaptor.getSignal(), + addMapForPrev, ValueRange{current_index}); auto signal_current = rewriter.create( - loc, findPeaksOpAdaptor.getSignal(), current_index); - auto signal_next = rewriter.create( - loc, findPeaksOpAdaptor.getSignal(), addMapForNext, ValueRange{current_index}); + loc, findPeaksOpAdaptor.getSignal(), current_index); + auto signal_next = + rewriter.create(loc, findPeaksOpAdaptor.getSignal(), + addMapForNext, ValueRange{current_index}); auto height = rewriter.create( - loc, findPeaksOpAdaptor.getHeight(), ValueRange{}); + loc, findPeaksOpAdaptor.getHeight(), ValueRange{}); //%cmp_current_prev = arith.cmpf ogt, %signal_current, %signal_prev : f64 //%cmp_current_next = arith.cmpf ogt, %signal_current, %signal_next : f64 @@ -7235,105 +7349,119 @@ struct FindPeaksOpLowering : public ConversionPattern { loc, arith::CmpFPredicate::OGT, signal_current, signal_next); auto cmp_current_height = rewriter.create( loc, arith::CmpFPredicate::OGE, signal_current, height); - + //%and_two_cmps = arith.andi %cmp_current_prev, %cmp_current_next : index - //%and_three_cmps = arith.andi %and_two_cmps, cmp_current_height : index - auto and_two_cmps = rewriter.create( - loc, cmp_current_prev, cmp_current_next); - auto and_three_cmps = rewriter.create( - loc, and_two_cmps, cmp_current_height); - - //scf.if %and_three_cmps { - auto firstIfOp = rewriter.create(loc, and_three_cmps, false /* else=1 */); + //%and_three_cmps = arith.andi %and_two_cmps, cmp_current_height : index + auto and_two_cmps = + rewriter.create(loc, cmp_current_prev, cmp_current_next); + auto and_three_cmps = + rewriter.create(loc, and_two_cmps, cmp_current_height); + + // scf.if %and_three_cmps { + auto firstIfOp = + rewriter.create(loc, and_three_cmps, false /* else=1 */); rewriter.setInsertionPointToStart(firstIfOp.thenBlock()); - + //%peaks_count = affine.load %alloc_peaks_count[] : memref //%cmp_new_peak = arith.cmpi eq, %peaks_count, %cst_zero_index : index auto peaks_count = rewriter.create( - loc, alloc_peaks_count, ValueRange{}); + loc, alloc_peaks_count, ValueRange{}); auto cmp_new_peak = rewriter.create( loc, arith::CmpIPredicate::eq, peaks_count, constant_index_zero); - //scf.if %cmp_new_peak { - // memref.store %current_index, %alloc_peaks[%peaks_count] : memref<10xindex> - // %peaks_count_inc = arith.addi %peaks_count, %cst_one_index : index - // affine.store %peaks_count_inc, %alloc_peaks_count[] : memref - //} - auto secondIfOp = rewriter.create(loc, cmp_new_peak, true /* else=1 */); + // scf.if %cmp_new_peak { + // memref.store %current_index, %alloc_peaks[%peaks_count] : + // memref<10xindex> %peaks_count_inc = arith.addi %peaks_count, + // %cst_one_index : index affine.store %peaks_count_inc, + // %alloc_peaks_count[] : memref + // } + auto secondIfOp = + rewriter.create(loc, cmp_new_peak, true /* else=1 */); rewriter.setInsertionPointToStart(secondIfOp.thenBlock()); - //index to f64 - Value current_index_to_ui = rewriter.create( + // index to f64 + Value current_index_to_ui = rewriter.create( loc, rewriter.getIntegerType(32), current_index); - Value current_index_to_f64 = - rewriter.create(loc, rewriter.getF64Type(), current_index_to_ui); - rewriter.create(loc, current_index_to_f64, alloc_output, ValueRange{peaks_count}); - auto peaks_count_inc = rewriter.create(loc, peaks_count, constant_index_one); - rewriter.create(loc, peaks_count_inc, alloc_peaks_count, ValueRange{}); - + Value current_index_to_f64 = rewriter.create( + loc, rewriter.getF64Type(), current_index_to_ui); + rewriter.create(loc, current_index_to_f64, alloc_output, + ValueRange{peaks_count}); + auto peaks_count_inc = + rewriter.create(loc, peaks_count, constant_index_one); + rewriter.create(loc, peaks_count_inc, alloc_peaks_count, + ValueRange{}); + /* else { %last_peaks_count = arith.subi %peaks_count, %cst_one_index : index - %last_peak_index = memref.load %alloc_peaks[%last_peaks_count] : memref<10xindex> - %subtract_current_index_last_peak = arith.subi %current_index, %last_peak_index : index - %cmp_sub_distance = arith.cmpi sge, %subtract_current_index_last_peak, %distance : index - */ + %last_peak_index = memref.load %alloc_peaks[%last_peaks_count] : + memref<10xindex> %subtract_current_index_last_peak = arith.subi + %current_index, %last_peak_index : index %cmp_sub_distance = arith.cmpi sge, + %subtract_current_index_last_peak, %distance : index + */ rewriter.setInsertionPointToStart(secondIfOp.elseBlock()); - //auto last_peak_index = rewriter.create(loc, alloc_output, addMapForPrev, ValueRange{peaks_count}); - //HWISOO: It does not work since it gives "error: 'affine.load' op index must be a valid dimension or symbol identifier" here. - Value last_peaks_count = rewriter.create(loc, peaks_count, constant_index_one); - auto last_peak_index_fp = rewriter.create(loc, alloc_output, ValueRange{last_peaks_count}); - //f64 to index - Value last_peak_index_ui = - rewriter.create(loc, rewriter.getIntegerType(32), last_peak_index_fp); - Value last_peak_index = rewriter.create( + // auto last_peak_index = rewriter.create(loc, alloc_output, + // addMapForPrev, ValueRange{peaks_count}); HWISOO: It does not work since + // it gives "error: 'affine.load' op index must be a valid dimension or + // symbol identifier" here. + Value last_peaks_count = + rewriter.create(loc, peaks_count, constant_index_one); + auto last_peak_index_fp = rewriter.create( + loc, alloc_output, ValueRange{last_peaks_count}); + // f64 to index + Value last_peak_index_ui = rewriter.create( + loc, rewriter.getIntegerType(32), last_peak_index_fp); + Value last_peak_index = rewriter.create( loc, rewriter.getIndexType(), last_peak_index_ui); - Value subtract_current_index_last_peak = rewriter.create(loc, current_index, last_peak_index); + Value subtract_current_index_last_peak = + rewriter.create(loc, current_index, last_peak_index); auto cmp_sub_distance = rewriter.create( - loc, arith::CmpIPredicate::sge, subtract_current_index_last_peak, distance); - - /* - scf.if %cmp_sub_distance { - memref.store %current_index, %alloc_peaks[%peaks_count] : memref<10xindex> - %peaks_count_inc = arith.addi %peaks_count, %cst_one_index : index - affine.store %peaks_count_inc, %alloc_peaks_count[] : memref - } - } - */ - auto thirdIfOp = rewriter.create(loc, cmp_sub_distance, true /* else=1 */); - rewriter.setInsertionPointToStart(thirdIfOp.thenBlock()); - //index to f64 - Value current_index_to_ui_2 = rewriter.create( + loc, arith::CmpIPredicate::sge, subtract_current_index_last_peak, + distance); + + /* + scf.if %cmp_sub_distance { + memref.store %current_index, %alloc_peaks[%peaks_count] : memref<10xindex> + %peaks_count_inc = arith.addi %peaks_count, %cst_one_index : index + affine.store %peaks_count_inc, %alloc_peaks_count[] : memref + } + } + */ + auto thirdIfOp = + rewriter.create(loc, cmp_sub_distance, true /* else=1 */); + rewriter.setInsertionPointToStart(thirdIfOp.thenBlock()); + // index to f64 + Value current_index_to_ui_2 = rewriter.create( loc, rewriter.getIntegerType(32), current_index); - Value current_index_to_f64_2 = - rewriter.create(loc, rewriter.getF64Type(), current_index_to_ui_2); - rewriter.create(loc, current_index_to_f64_2, alloc_output, ValueRange{peaks_count}); - auto peaks_count_inc_2 = rewriter.create(loc, peaks_count, constant_index_one); - rewriter.create(loc, peaks_count_inc_2, alloc_peaks_count, ValueRange{}); + Value current_index_to_f64_2 = rewriter.create( + loc, rewriter.getF64Type(), current_index_to_ui_2); + rewriter.create(loc, current_index_to_f64_2, alloc_output, + ValueRange{peaks_count}); + auto peaks_count_inc_2 = + rewriter.create(loc, peaks_count, constant_index_one); + rewriter.create(loc, peaks_count_inc_2, alloc_peaks_count, + ValueRange{}); rewriter.setInsertionPointAfter(forOpSignal); - /* Setting last element of the output as the count of peaks. - Note that last-last ([-2]) should be always -1. */ - auto peaks_count_final = rewriter.create(loc, alloc_peaks_count, ValueRange{}); - //index to f64 - Value peaks_count_final_to_ui = rewriter.create( + /* Setting last element of the output as the count of peaks. + Note that last-last ([-2]) should be always -1. */ + auto peaks_count_final = rewriter.create( + loc, alloc_peaks_count, ValueRange{}); + // index to f64 + Value peaks_count_final_to_ui = rewriter.create( loc, rewriter.getIntegerType(32), peaks_count_final); - Value peaks_count_final_to_f64 = - rewriter.create(loc, rewriter.getF64Type(), peaks_count_final_to_ui); - rewriter.create(loc, peaks_count_final_to_f64, alloc_output, addMapForPrev, ValueRange{constant_len_singal}); + Value peaks_count_final_to_f64 = rewriter.create( + loc, rewriter.getF64Type(), peaks_count_final_to_ui); + rewriter.create(loc, peaks_count_final_to_f64, alloc_output, + addMapForPrev, + ValueRange{constant_len_singal}); - rewriter.replaceOp(op, alloc_output); - + return success(); - } + } }; - - - - - //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: Unary operations //===----------------------------------------------------------------------===// @@ -7782,247 +7910,275 @@ struct GenerateDTMFOpLowering : public ConversionPattern { {770, 1336}, {770, 1477}, {852, 1209}, {852, 1336}, {852, 1477}, {941, 1209}, {941, 1336}, {941, 1477}}; + // auto inputval = generatedtmfAdaptor.getInput(); + // Value GetDigitInput = op->getOperand(0); + // dsp::ConstantOp inputvl = inputval.getDefiningOp(); + // DenseElementsAttr inputvalue = inputvl.getValue(); + // auto elements1 = inputvalue.getValues(); + // float input = elements1[0].getValueAsDouble(); -// auto inputval = generatedtmfAdaptor.getInput(); - -// Value GetDigitInput = op->getOperand(0); -// dsp::ConstantOp inputvl = inputval.getDefiningOp(); -// DenseElementsAttr inputvalue = inputvl.getValue(); -// auto elements1 = inputvalue.getValues(); -// float input = elements1[0].getValueAsDouble(); + // Value GetDurationOp = op->getOperand(1); + // dsp::ConstantOp constantOp2ndArg = + // GetDurationOp.getDefiningOp(); + // DenseElementsAttr constant2ndValue = constantOp2ndArg.getValue(); + // auto elements2 = constant2ndValue.getValues(); + // float duration = elements2[0].getValueAsDouble(); -// Value GetDurationOp = op->getOperand(1); -// dsp::ConstantOp constantOp2ndArg = -// GetDurationOp.getDefiningOp(); -// DenseElementsAttr constant2ndValue = constantOp2ndArg.getValue(); -// auto elements2 = constant2ndValue.getValues(); -// float duration = elements2[0].getValueAsDouble(); + // Value GetFreqOp = op->getOperand(2); + // dsp::ConstantOp constantOp3rdArg = + // GetFreqOp.getDefiningOp(); + // DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue(); + // auto elements3 = constant3rdValue.getValues(); + // float freq = elements3[0].getValueAsDouble(); -// Value GetFreqOp = op->getOperand(2); -// dsp::ConstantOp constantOp3rdArg = -// GetFreqOp.getDefiningOp(); -// DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue(); -// auto elements3 = constant3rdValue.getValues(); -// float freq = elements3[0].getValueAsDouble(); + // const std::vector &pair = freqPairs[input]; + // int64_t f1 = pair[0]; + // int64_t f2 = pair[1]; + // int64_t lb = 0; + // int64_t ub = tensorType.getShape()[0]; + // int64_t step = 1; + // Value const2pi = rewriter.create( + // loc, rewriter.getF64Type(), + // rewriter.getF64FloatAttr(6.28318530718)); -// const std::vector &pair = freqPairs[input]; -// int64_t f1 = pair[0]; -// int64_t f2 = pair[1]; -// int64_t lb = 0; -// int64_t ub = tensorType.getShape()[0]; -// int64_t step = 1; - -// Value const2pi = rewriter.create( -// loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(6.28318530718)); - -// Value mulProd = rewriter.create(loc, getLhs, getRhs); -// auto yMemRefType = MemRefType::get({ub}, rewriter.getF64Type()); -// auto tAlloc = rewriter.create(loc, yMemRefType); + // Value mulProd = rewriter.create(loc, getLhs, getRhs); + // auto yMemRefType = MemRefType::get({ub}, rewriter.getF64Type()); + // auto tAlloc = rewriter.create(loc, yMemRefType); + // // for loop from 0 to len(Output) + // affine::AffineForOp forOpY = + // rewriter.create(loc, lb, ub, step); + // auto ivY = forOpY.getInductionVar(); + // rewriter.setInsertionPointToStart(forOpY.getBody()); - + // Value inputX = + // rewriter.create(loc, thresholdUpAdaptor.getInput(), + // ivY); -// // for loop from 0 to len(Output) -// affine::AffineForOp forOpY = -// rewriter.create(loc, lb, ub, step); -// auto ivY = forOpY.getInductionVar(); -// rewriter.setInsertionPointToStart(forOpY.getBody()); + // // Store the result + // rewriter.create(loc, selectOp, alloc, ivY); -// Value inputX = -// rewriter.create(loc, thresholdUpAdaptor.getInput(), ivY); + // rewriter.setInsertionPointAfter(forOpY); - -// // Store the result -// rewriter.create(loc, selectOp, alloc, ivY); - -// rewriter.setInsertionPointAfter(forOpY); - - - -// rewriter.replaceOp(op, alloc); + // rewriter.replaceOp(op, alloc); return success(); } }; struct QamModulateRealOpLowering : public ConversionPattern { - QamModulateRealOpLowering(MLIRContext *ctx) : - ConversionPattern(dsp::QamModulateRealOp::getOperationName(), 1, ctx) {} - - LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { - auto loc = op->getLoc(); + QamModulateRealOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::QamModulateRealOp::getOperationName(), 1, ctx) {} - auto output = llvm::dyn_cast((*op->result_type_begin())); - auto outputMem = convertTensorToMemRef(output); - auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); - - QamModulateRealOpAdaptor adaptor(operands); - Value signal = adaptor.getSignal(); + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); - llvm::ArrayRef outputShape = output.getShape(); + QamModulateRealOpAdaptor adaptor(operands); + Value signal = adaptor.getSignal(); - // constant vals; - Value negOneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); - Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - Value oneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + llvm::ArrayRef outputShape = output.getShape(); - AffineExpr d0; // declare one dimension affine expr - bindDims(rewriter.getContext(), d0); // bind affine expr d0 to current input (real array) dimension + // constant vals; + Value negOneVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + Value zeroVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value oneVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); - // real affine map - AffineMap signalMap = AffineMap::get(1, 0, ArrayRef{d0*2}, rewriter.getContext()); - // output affine map - AffineMap outputRealMap = AffineMap::get(1, 0, ArrayRef{d0}, rewriter.getContext()); + AffineExpr d0; // declare one dimension affine expr + bindDims(rewriter.getContext(), + d0); // bind affine expr d0 to current input (real array) dimension - // loops - int64_t lb=0, step=1, ub=outputShape[0]; - /* looping i*/ - AffineForOp forOpI = rewriter.create(loc, lb, ub, step); - rewriter.setInsertionPointToStart(forOpI.getBody()); - auto ivI = forOpI.getInductionVar(); + // real affine map + AffineMap signalMap = AffineMap::get(1, 0, ArrayRef{d0 * 2}, + rewriter.getContext()); + // output affine map + AffineMap outputRealMap = + AffineMap::get(1, 0, ArrayRef{d0}, rewriter.getContext()); + // loops + int64_t lb = 0, step = 1, ub = outputShape[0]; + /* looping i*/ + AffineForOp forOpI = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOpI.getBody()); + auto ivI = forOpI.getInductionVar(); - // input bound check - Value signalNum = rewriter.create(loc, signal, signalMap, ValueRange{ivI}); + // input bound check + Value signalNum = + rewriter.create(loc, signal, signalMap, ValueRange{ivI}); - Value zeroReal = rewriter.create(loc, arith::CmpFPredicate::OEQ, signalNum, zeroVal); + Value zeroReal = rewriter.create( + loc, arith::CmpFPredicate::OEQ, signalNum, zeroVal); - Value out = rewriter.create(loc, zeroReal, negOneVal, oneVal); + Value out = + rewriter.create(loc, zeroReal, negOneVal, oneVal); - rewriter.create(loc, out, alloc, outputRealMap, ValueRange{ivI}); - rewriter.setInsertionPointAfter(forOpI); - rewriter.replaceOp(op, alloc); + rewriter.create(loc, out, alloc, outputRealMap, + ValueRange{ivI}); + rewriter.setInsertionPointAfter(forOpI); + rewriter.replaceOp(op, alloc); - return success(); - } + return success(); + } }; struct QamModulateImgOpLowering : public ConversionPattern { - QamModulateImgOpLowering(MLIRContext *ctx) : - ConversionPattern(dsp::QamModulateImgOp::getOperationName(), 1, ctx) {} - - LogicalResult matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { - auto loc = op->getLoc(); + QamModulateImgOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::QamModulateImgOp::getOperationName(), 1, ctx) {} - auto output = llvm::dyn_cast((*op->result_type_begin())); - auto outputMem = convertTensorToMemRef(output); - auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); - - QamModulateRealOpAdaptor adaptor(operands); - Value signal = adaptor.getSignal(); + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); - llvm::ArrayRef outputShape = output.getShape(); + QamModulateRealOpAdaptor adaptor(operands); + Value signal = adaptor.getSignal(); - // constant vals; - Value negOneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); - Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - Value oneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + llvm::ArrayRef outputShape = output.getShape(); - AffineExpr d0; // declare one dimension affine expr - bindDims(rewriter.getContext(), d0); // bind affine expr d0 to current input (real array) dimension + // constant vals; + Value negOneVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + Value zeroVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value oneVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); - // real affine map - AffineMap signalMap = AffineMap::get(1, 0, ArrayRef{d0*2+1}, rewriter.getContext()); - // output affine map - AffineMap outputImgMap = AffineMap::get(1, 0, ArrayRef{d0}, rewriter.getContext()); + AffineExpr d0; // declare one dimension affine expr + bindDims(rewriter.getContext(), + d0); // bind affine expr d0 to current input (real array) dimension - // loops - int64_t lb=0, step=1, ub=outputShape[0]; - /* looping i*/ - AffineForOp forOpI = rewriter.create(loc, lb, ub, step); - rewriter.setInsertionPointToStart(forOpI.getBody()); - auto ivI = forOpI.getInductionVar(); - + // real affine map + AffineMap signalMap = AffineMap::get(1, 0, ArrayRef{d0 * 2 + 1}, + rewriter.getContext()); + // output affine map + AffineMap outputImgMap = + AffineMap::get(1, 0, ArrayRef{d0}, rewriter.getContext()); - // input bound check - Value signalNum = rewriter.create(loc, signal, signalMap, ValueRange{ivI}); + // loops + int64_t lb = 0, step = 1, ub = outputShape[0]; + /* looping i*/ + AffineForOp forOpI = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOpI.getBody()); + auto ivI = forOpI.getInductionVar(); - Value zeroReal = rewriter.create(loc, arith::CmpFPredicate::OEQ, signalNum, zeroVal); + // input bound check + Value signalNum = + rewriter.create(loc, signal, signalMap, ValueRange{ivI}); - Value out = rewriter.create(loc, zeroReal, negOneVal, oneVal); + Value zeroReal = rewriter.create( + loc, arith::CmpFPredicate::OEQ, signalNum, zeroVal); - rewriter.create(loc, out, alloc, outputImgMap, ValueRange{ivI}); + Value out = + rewriter.create(loc, zeroReal, negOneVal, oneVal); - rewriter.setInsertionPointAfter(forOpI); - rewriter.replaceOp(op, alloc); + rewriter.create(loc, out, alloc, outputImgMap, + ValueRange{ivI}); - return success(); + rewriter.setInsertionPointAfter(forOpI); + rewriter.replaceOp(op, alloc); - } + return success(); + } }; //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: QAM demodulate operations //===----------------------------------------------------------------------===// struct QamDemodulateOpLowering : public ConversionPattern { - QamDemodulateOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::QamDemodulateOp::getOperationName(), 1, ctx) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - - auto loc = op->getLoc(); - // output mem alloc and dealloc - auto output = llvm::dyn_cast((*op->result_type_begin())); - auto outputMem = convertTensorToMemRef(output); - auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); - - QamDemodulateOpAdaptor qamDemodualteAdaptor(operands); - Value realVal = qamDemodualteAdaptor.getReal(); - Value imgVal = qamDemodualteAdaptor.getImagine(); - - // ranked tensor type - auto realType = llvm::dyn_cast(op->getOperand(0).getType()); - - llvm::ArrayRef realShape = realType.getShape(); - llvm::ArrayRef outputShape = output.getShape(); - - // constant vals; - Value negOneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); - Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - Value oneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); - - AffineExpr d0; // declare one dimension affine expr - bindDims(rewriter.getContext(), d0); // bind affine expr d0 to current input (real array) dimension - - // real affine map - AffineMap realMap = AffineMap::get(1, 0, ArrayRef{d0}, rewriter.getContext()); - // imagine affine map - AffineMap imgMap = AffineMap::get(1, 0, ArrayRef{d0}, rewriter.getContext()); - // output affine map - AffineMap outputMapReal = AffineMap::get(1, 0, ArrayRef{d0*2}, rewriter.getContext()); - AffineMap outputMapImagine = AffineMap::get(1, 0, ArrayRef{d0*2+1}, rewriter.getContext()); - - // loops - int64_t lb=0, step=1, ub=realShape[0]; - /* looping i*/ - AffineForOp forOpI = rewriter.create(loc, lb, ub, step); - rewriter.setInsertionPointToStart(forOpI.getBody()); - auto ivI = forOpI.getInductionVar(); - - - // input bound check - Value realNum = rewriter.create(loc, realVal, realMap, ValueRange{ivI}); - Value imgNum = rewriter.create(loc, imgVal, imgMap, ValueRange{ivI}); - - Value negReal = rewriter.create(loc, arith::CmpFPredicate::OEQ, realNum, negOneVal); - Value negImagine = rewriter.create(loc, arith::CmpFPredicate::OEQ, imgNum, negOneVal); - - Value out1 = rewriter.create(loc, negReal, zeroVal, oneVal); - Value out2 = rewriter.create(loc, negImagine, zeroVal, oneVal); - - rewriter.create(loc, out1, alloc, outputMapReal, ValueRange{ivI}); - rewriter.create(loc, out2, alloc, outputMapImagine, ValueRange{ivI}); - - rewriter.replaceOp(op, alloc); - - return success(); - } + QamDemodulateOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::QamDemodulateOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + + auto loc = op->getLoc(); + // output mem alloc and dealloc + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); + + QamDemodulateOpAdaptor qamDemodualteAdaptor(operands); + Value realVal = qamDemodualteAdaptor.getReal(); + Value imgVal = qamDemodualteAdaptor.getImagine(); + + // ranked tensor type + auto realType = + llvm::dyn_cast(op->getOperand(0).getType()); + + llvm::ArrayRef realShape = realType.getShape(); + llvm::ArrayRef outputShape = output.getShape(); + + // constant vals; + Value negOneVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + Value zeroVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value oneVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + + AffineExpr d0; // declare one dimension affine expr + bindDims(rewriter.getContext(), + d0); // bind affine expr d0 to current input (real array) dimension + + // real affine map + AffineMap realMap = + AffineMap::get(1, 0, ArrayRef{d0}, rewriter.getContext()); + // imagine affine map + AffineMap imgMap = + AffineMap::get(1, 0, ArrayRef{d0}, rewriter.getContext()); + // output affine map + AffineMap outputMapReal = AffineMap::get(1, 0, ArrayRef{d0 * 2}, + rewriter.getContext()); + AffineMap outputMapImagine = AffineMap::get( + 1, 0, ArrayRef{d0 * 2 + 1}, rewriter.getContext()); + + // loops + int64_t lb = 0, step = 1, ub = realShape[0]; + /* looping i*/ + AffineForOp forOpI = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOpI.getBody()); + auto ivI = forOpI.getInductionVar(); + + // input bound check + Value realNum = + rewriter.create(loc, realVal, realMap, ValueRange{ivI}); + Value imgNum = + rewriter.create(loc, imgVal, imgMap, ValueRange{ivI}); + + Value negReal = rewriter.create( + loc, arith::CmpFPredicate::OEQ, realNum, negOneVal); + Value negImagine = rewriter.create( + loc, arith::CmpFPredicate::OEQ, imgNum, negOneVal); + + Value out1 = + rewriter.create(loc, negReal, zeroVal, oneVal); + Value out2 = + rewriter.create(loc, negImagine, zeroVal, oneVal); + + rewriter.create(loc, out1, alloc, outputMapReal, + ValueRange{ivI}); + rewriter.create(loc, out2, alloc, outputMapImagine, + ValueRange{ivI}); + + rewriter.replaceOp(op, alloc); + + return success(); + } }; // qam_demodulate op //===----------------------------------------------------------------------===// @@ -8030,131 +8186,144 @@ struct QamDemodulateOpLowering : public ConversionPattern { //===----------------------------------------------------------------------===// struct BeamFormOpLowering : public ConversionPattern { - BeamFormOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::BeamFormOp::getOperationName(), 1, ctx) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - auto loc = op->getLoc(); - auto beamFormOp = llvm::dyn_cast(op); - - // allocating space for output - auto output = llvm::dyn_cast((*op->result_type_begin())); - llvm::errs() << "output case.\n"; - auto outputMemRefType = convertTensorToMemRef(output); - auto alloc = insertAllocAndDealloc(outputMemRefType, loc, rewriter); - - BeamFormOpAdaptor beamFormAdaptor(operands); - auto time = beamFormAdaptor.getTime(); - auto weights = beamFormAdaptor.getWeights(); - - // allocating space for internal generated signals - auto timeDim = output.getShape()[0]; //dry run: 9 - int64_t antennas = beamFormOp.getAntennas(); - int64_t frequency = beamFormOp.getFreq(); - - llvm::ArrayRef signalShape{antennas, timeDim}; - auto signalType = output.clone(signalShape); - - auto signalMemRefType = convertTensorToMemRef(signalType); - auto allocSignal = insertAllocAndDealloc(signalMemRefType, loc, rewriter); - llvm::errs() << "signal alloc\n"; - - AffineExpr d0, d1; // i, j for generated signal dimension - bindDims(rewriter.getContext(), d0, d1); - - // generated input map - AffineMap genInputMap = AffineMap::get( - 2 /* dim */, 0 /* sym */, ArrayRef{d0, d1}, rewriter.getContext() - ); - // time affine map - AffineMap timeMap = AffineMap::get( - 2 /* dim */, 0 /* sym */, ArrayRef{d1}, rewriter.getContext() - ); - - // output map - AffineMap outputMap = AffineMap::get( - 2, 0, ArrayRef{d0}, rewriter.getContext() - ); - - auto pi = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.1415926)); - auto two = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(2)); - auto four = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(4)); - auto two_pi = rewriter.create(loc, pi, two); // 2 * pi - auto freq_val = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(frequency)); - auto phase_var = rewriter.create(loc, two_pi, freq_val); - llvm::errs() << "const alloc\n"; - - // for loop from 0 to phase - int64_t lb = 0, ub = antennas, step=1; - affine::AffineForOp forOpI = rewriter.create(loc, lb, ub, step); - auto ivI = forOpI.getInductionVar(); // i : phase - rewriter.setInsertionPointToStart(forOpI.getBody()); - llvm::errs() << "first loop\n"; - - // get the induction var to phase variable - auto intType = rewriter.getI64Type(); - auto intI = rewriter.create(loc, intType, ivI); - auto floatType = rewriter.getF64Type(); - auto floatI = rewriter.create(loc, floatType, intI); - - auto iter_tmp = rewriter.create(loc, floatI, pi); // i * pi - auto iter_args = rewriter.create(loc, iter_tmp, four); - - // for loop from 0 to timeDim - ub = timeDim; - affine::AffineForOp forOpJ = rewriter.create(loc, lb, ub, step); - auto ivJ = forOpJ.getInductionVar(); // i : phase - rewriter.setInsertionPointToStart(forOpJ.getBody()); - llvm::errs() << "second loop\n"; - - // loop body - auto time_var = rewriter.create(loc, time, timeMap, ValueRange{ivI, ivJ}); - auto mul_var = rewriter.create(loc, time_var, phase_var); - auto sin_body = rewriter.create(loc, mul_var, iter_args); - auto result = rewriter.create(loc, sin_body); - llvm::errs() << "body loop\n"; - rewriter.create(loc, result, allocSignal, ValueRange{ivI, ivJ}); - - forOpJ.dump(); - rewriter.setInsertionPointAfter(forOpJ); - rewriter.setInsertionPointAfter(forOpI); - - ub = antennas; - affine::AffineForOp forOpIOut = rewriter.create(loc, lb, ub, step); - auto ivIoutput = forOpIOut.getInductionVar(); - rewriter.setInsertionPointToStart(forOpIOut.getBody()); - llvm::errs() << "beam 1 loop\n"; - - ub = timeDim; - affine::AffineForOp forOpJOut = rewriter.create(loc, lb, ub, step); - auto ivJoutput = forOpJOut.getInductionVar(); - rewriter.setInsertionPointToStart(forOpJOut.getBody()); - llvm::errs() << "beam 2 loop\n"; - - // load from signal input - auto signalInput = rewriter.create(loc, allocSignal, genInputMap, ValueRange{ivIoutput, ivJoutput}); - auto weight = rewriter.create(loc, weights, outputMap, ValueRange{ivIoutput, ivJoutput}); - auto intermediateVal = rewriter.create(loc, signalInput, weight); - llvm::errs() << "load from signal input \n"; - - // load from output - auto outputVal = rewriter.create(loc, alloc, ValueRange{ivJoutput}); - auto beamOut = rewriter.create(loc, intermediateVal, outputVal); - llvm::errs() << "load from bean output\n"; - - rewriter.create(loc, beamOut, alloc, ValueRange{ivJoutput}); - - rewriter.setInsertionPointAfter(forOpJOut); - rewriter.setInsertionPointAfter(forOpIOut); - - rewriter.replaceOp(op, alloc); - - llvm::errs() << "success loop\n"; - return mlir::success(); - - } + BeamFormOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::BeamFormOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + auto beamFormOp = llvm::dyn_cast(op); + + // allocating space for output + auto output = llvm::dyn_cast((*op->result_type_begin())); + llvm::errs() << "output case.\n"; + auto outputMemRefType = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMemRefType, loc, rewriter); + + BeamFormOpAdaptor beamFormAdaptor(operands); + auto time = beamFormAdaptor.getTime(); + auto weights = beamFormAdaptor.getWeights(); + + // allocating space for internal generated signals + auto timeDim = output.getShape()[0]; // dry run: 9 + int64_t antennas = beamFormOp.getAntennas(); + int64_t frequency = beamFormOp.getFreq(); + + llvm::ArrayRef signalShape{antennas, timeDim}; + auto signalType = output.clone(signalShape); + + auto signalMemRefType = convertTensorToMemRef(signalType); + auto allocSignal = insertAllocAndDealloc(signalMemRefType, loc, rewriter); + llvm::errs() << "signal alloc\n"; + + AffineExpr d0, d1; // i, j for generated signal dimension + bindDims(rewriter.getContext(), d0, d1); + + // generated input map + AffineMap genInputMap = + AffineMap::get(2 /* dim */, 0 /* sym */, ArrayRef{d0, d1}, + rewriter.getContext()); + // time affine map + AffineMap timeMap = + AffineMap::get(2 /* dim */, 0 /* sym */, ArrayRef{d1}, + rewriter.getContext()); + + // output map + AffineMap outputMap = + AffineMap::get(2, 0, ArrayRef{d0}, rewriter.getContext()); + + auto pi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.1415926)); + auto two = rewriter.create(loc, rewriter.getF64Type(), + rewriter.getF64FloatAttr(2)); + auto four = rewriter.create(loc, rewriter.getF64Type(), + rewriter.getF64FloatAttr(4)); + auto two_pi = rewriter.create(loc, pi, two); // 2 * pi + auto freq_val = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(frequency)); + auto phase_var = rewriter.create(loc, two_pi, freq_val); + llvm::errs() << "const alloc\n"; + + // for loop from 0 to phase + int64_t lb = 0, ub = antennas, step = 1; + affine::AffineForOp forOpI = + rewriter.create(loc, lb, ub, step); + auto ivI = forOpI.getInductionVar(); // i : phase + rewriter.setInsertionPointToStart(forOpI.getBody()); + llvm::errs() << "first loop\n"; + + // get the induction var to phase variable + auto intType = rewriter.getI64Type(); + auto intI = rewriter.create(loc, intType, ivI); + auto floatType = rewriter.getF64Type(); + auto floatI = rewriter.create(loc, floatType, intI); + + auto iter_tmp = rewriter.create(loc, floatI, pi); // i * pi + auto iter_args = rewriter.create(loc, iter_tmp, four); + + // for loop from 0 to timeDim + ub = timeDim; + affine::AffineForOp forOpJ = + rewriter.create(loc, lb, ub, step); + auto ivJ = forOpJ.getInductionVar(); // i : phase + rewriter.setInsertionPointToStart(forOpJ.getBody()); + llvm::errs() << "second loop\n"; + + // loop body + auto time_var = + rewriter.create(loc, time, timeMap, ValueRange{ivI, ivJ}); + auto mul_var = rewriter.create(loc, time_var, phase_var); + auto sin_body = rewriter.create(loc, mul_var, iter_args); + auto result = rewriter.create(loc, sin_body); + llvm::errs() << "body loop\n"; + rewriter.create(loc, result, allocSignal, + ValueRange{ivI, ivJ}); + + forOpJ.dump(); + rewriter.setInsertionPointAfter(forOpJ); + rewriter.setInsertionPointAfter(forOpI); + + ub = antennas; + affine::AffineForOp forOpIOut = + rewriter.create(loc, lb, ub, step); + auto ivIoutput = forOpIOut.getInductionVar(); + rewriter.setInsertionPointToStart(forOpIOut.getBody()); + llvm::errs() << "beam 1 loop\n"; + + ub = timeDim; + affine::AffineForOp forOpJOut = + rewriter.create(loc, lb, ub, step); + auto ivJoutput = forOpJOut.getInductionVar(); + rewriter.setInsertionPointToStart(forOpJOut.getBody()); + llvm::errs() << "beam 2 loop\n"; + + // load from signal input + auto signalInput = rewriter.create( + loc, allocSignal, genInputMap, ValueRange{ivIoutput, ivJoutput}); + auto weight = rewriter.create( + loc, weights, outputMap, ValueRange{ivIoutput, ivJoutput}); + auto intermediateVal = + rewriter.create(loc, signalInput, weight); + llvm::errs() << "load from signal input \n"; + + // load from output + auto outputVal = + rewriter.create(loc, alloc, ValueRange{ivJoutput}); + auto beamOut = + rewriter.create(loc, intermediateVal, outputVal); + llvm::errs() << "load from bean output\n"; + + rewriter.create(loc, beamOut, alloc, ValueRange{ivJoutput}); + + rewriter.setInsertionPointAfter(forOpJOut); + rewriter.setInsertionPointAfter(forOpIOut); + + rewriter.replaceOp(op, alloc); + + llvm::errs() << "success loop\n"; + return mlir::success(); + } }; } // namespace @@ -8174,7 +8343,7 @@ struct ToyToAffineLoweringPass void getDependentDialects(DialectRegistry ®istry) const override { registry .insert(); + math::MathDialect, scf::SCFDialect>(); } void runOnOperation() final; }; @@ -8229,7 +8398,9 @@ void ToyToAffineLoweringPass::runOnOperation() { FIRFilterYSymmOptimizedOpLowering, FFT1DRealSymmOpLowering, FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering, ShiftRightOpLowering, MatmulOpLowering, - ThresholdUpOpLowering, QamModulateRealOpLowering, QamModulateImgOpLowering, QamDemodulateOpLowering, FindPeaksOpLowering, BeamFormOpLowering>(&getContext()); + ThresholdUpOpLowering, QamModulateRealOpLowering, + QamModulateImgOpLowering, QamDemodulateOpLowering, FindPeaksOpLowering, + BeamFormOpLowering, MedianFilterOpLowering>(&getContext()); // With the target and rewrite patterns defined, we can now attempt the // conversion. The conversion will signal failure if any of our `illegal` diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index ff56ef6d95fa..504d203524c2 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -460,6 +460,15 @@ class MLIRGenImpl { operands[1]); } + if(callee == "medianFilter"){ + if(call.getArgs().size() != 1){ + emitError(location, "MLIR codegen encountered an error: dsp.medianFilter " + "accepts only 1 argument"); + return nullptr; + } + return builder.create(location, operands[0] ); + } + if (callee == "slidingWindowAvg") { if (call.getArgs().size() != 1) { emitError(location, diff --git a/mlir/test/Examples/DspExample/dsp_fft.py b/mlir/test/Examples/DspExample/dsp_fft.py index e3d8b8f9b949..6547f88bd658 100644 --- a/mlir/test/Examples/DspExample/dsp_fft.py +++ b/mlir/test/Examples/DspExample/dsp_fft.py @@ -1,5 +1,5 @@ def main() { - var a = [0.0, 10.0, 340.0, 30.0, 40.0, 110.0, 60.0, 250.0]; + var a = [1,2,3,4,5,6,7,8]; var b = fftReal(a); var c = fftImag(a); print(b); diff --git a/mlir/test/Examples/DspExample/dsp_medfilt.py b/mlir/test/Examples/DspExample/dsp_medfilt.py new file mode 100644 index 000000000000..e19f039d4b8d --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_medfilt.py @@ -0,0 +1,18 @@ +def main() { + var a = [0.0, 10.0, 340.0, 30.0, 40.0, 110.0, 60.0, 250.0]; + var b = medianFilter(a); + print(b); +} + +# emit=mlir +# ----------- +# module { +# dsp.func @main() { +# %0 = dsp.constant dense<[0.000000e+00, 1.000000e+01, 3.400000e+02, 3.000000e+01, 4.000000e+01, 1.100000e+02, 6.000000e+01, 2.500000e+02]> : tensor<8xf64> +# %1 = "dsp.medianFilter"(%0) : (tensor<8xf64>) -> tensor<*xf64> +# dsp.print %1 : tensor<*xf64> +# dsp.return +# } +# } + +# emit=mlir-affine diff --git a/mlir/test/Examples/DspExample/dsp_signal_smoothing.py b/mlir/test/Examples/DspExample/dsp_signal_smoothing.py new file mode 100644 index 000000000000..fcee96119ba3 --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_signal_smoothing.py @@ -0,0 +1,7 @@ +def main() { + var a = [0.0, 10.0, 340.0, 30.0, 40.0, 110.0, 60.0, 250.0]; + var b = slidingWindowAvg(a); + var c = medianFilter(b); + print(c); +} + From 7998af64d747cab9f8f43250f52ded9d1d6cdd5e Mon Sep 17 00:00:00 2001 From: "Kuo, Mei-Chun" <94007620+Megan0704-1@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:03:21 -0700 Subject: [PATCH 18/45] beam form application and bug fix (#23) --- .../TryDSPApps/BenchmarkTest/CCode/beamForm.c | 78 +++++++++++++++++++ .../BenchmarkTest/DSP-DSL/beamForm.py | 11 +++ .../BenchmarkTest/Matlab/beamForm.m | 36 +++++++++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 68 ++++++++-------- .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 33 ++++---- .../test/Examples/DspExample/dsp_beam_form.py | 3 +- 6 files changed, 176 insertions(+), 53 deletions(-) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/beamForm.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/beamForm.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/beamForm.m diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/beamForm.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/beamForm.c new file mode 100644 index 000000000000..42af1363f874 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/beamForm.c @@ -0,0 +1,78 @@ +#include +#include +#include + +#define INPUT_LENGTH 100 +#define WEIGHT_LENGTH 4 +#define NUM_ANTENNAS WEIGHT_LENGTH +#define FREQUENCY 5 // Hz +#define PI 3.14159265359 +#define STARTTIME 0 +#define ENDTIME 99 + +void generate_time(double *time) { + double start = 0.0; + double end = 99.0; + double step = (end - start) / (INPUT_LENGTH - 1); + for (int i = 0; i < INPUT_LENGTH; i++) { + time[i] = start + i * step; + } +} + + +// Generate input signals for each antenna +void generate_signals(double** signals, double* time) { + for (int i = 0; i < NUM_ANTENNAS; i++) { + double phase_shift = i * PI / 4.0; // Different phase for each antenna + for (int j = 0; j < INPUT_LENGTH; j++) { + double time = j; + signals[i][j] = sin(2.0 * PI * FREQUENCY * time + phase_shift); + } + } +} + +void beamform_signal(double** input_signals, double *weights, double *beamformed_signal) { + for (int j = 0; j < INPUT_LENGTH; j++) { + beamformed_signal[j] = 0.0; + for (int i = 0; i < NUM_ANTENNAS; i++) { + beamformed_signal[j] += input_signals[i][j] * weights[i]; + } + } +} + +int main() { + double* time; // [INPUT_LENGTH]; + double** input_signals; // [NUM_ANTENNAS][INPUT_LENGTH]; + double* weights; // [NUM_ANTENNAS] = {1, 2, 3, 4}; // Example weights for alignment + double* beamformed_signal; // [INPUT_LENGTH]; + + time = (double*)malloc(INPUT_LENGTH * sizeof(double)); + generate_time(time); + + input_signals = (double**)malloc(NUM_ANTENNAS * sizeof(double*)); + for (int i = 0; i < NUM_ANTENNAS; i++) { + input_signals[i] = (double*)malloc(INPUT_LENGTH * sizeof(double)); + } + generate_signals(input_signals, time); + + weights = (double*)malloc(NUM_ANTENNAS * sizeof(double)); + for(int i=1; i<=NUM_ANTENNAS; i++) { + weights[i-1] = i; + } + + beamformed_signal = (double*)malloc(INPUT_LENGTH * sizeof(double)); + beamform_signal(input_signals, weights, beamformed_signal); + + // Print the beamformed signal + for (int j = 0; j < INPUT_LENGTH; j++) { + printf("Beamformed Signal[%d]: %f\n", j, beamformed_signal[j]); + } + + for(int i=0; i(getTime().getType()); - auto weightType = llvm::dyn_cast(getWeights().getType()); - - if(!timeType) { - llvm::errs() << "expect a ranked tensor for time input array."; - return mlir::failure(); - } - if(!weightType){ - llvm::errs() << "expect a ranked tensor for weight input array."; - return mlir::failure(); - } - - auto timeShape = timeType.getShape(); - auto timeRank = timeType.getRank(); - auto weightShape = weightType.getShape(); - auto weightRank = weightType.getRank(); - - if(timeRank != 1) { - llvm::errs() << "expect input time array to be 1 dim.\n"; - return mlir::failure(); - } - if(weightRank != 1) { - llvm::errs() << "expect input weight array to be 2 dim.\n"; - return mlir::failure(); - } - - auto antennas = getAntennas(); - llvm::errs() << "mk type check, antenna value: " << antennas << "\n"; - - auto shape = weightShape[0]; - if(shape != antennas) { - llvm::errs() << "expect weight to have shape: [" << antennas << "]\n"; - return mlir::failure(); - } + // auto timeType = llvm::dyn_cast(getTime().getType()); + // auto weightType = llvm::dyn_cast(getWeights().getType()); + + // if(!timeType) { + // llvm::errs() << "expect a ranked tensor for time input array."; + // return mlir::failure(); + // } + // if(!weightType){ + // llvm::errs() << "expect a ranked tensor for weight input array."; + // return mlir::failure(); + // } + + // auto timeShape = timeType.getShape(); + // auto timeRank = timeType.getRank(); + // auto weightShape = weightType.getShape(); + // auto weightRank = weightType.getRank(); + + // if(timeRank != 1) { + // llvm::errs() << "expect input time array to be 1 dim.\n"; + // return mlir::failure(); + // } + // if(weightRank != 1) { + // llvm::errs() << "expect input weight array to be 2 dim.\n"; + // return mlir::failure(); + // } + + // auto antennas = getAntennas(); + // llvm::errs() << "mk type check, antenna value: " << antennas << "\n"; + + // auto shape = weightShape[0]; + // if(shape != antennas) { + // llvm::errs() << "expect weight to have shape: [" << antennas << "]\n"; + // return mlir::failure(); + // } return mlir::success(); } diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index bee3f94145d9..cdcc91133206 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -8215,7 +8215,6 @@ struct BeamFormOpLowering : public ConversionPattern { auto signalMemRefType = convertTensorToMemRef(signalType); auto allocSignal = insertAllocAndDealloc(signalMemRefType, loc, rewriter); - llvm::errs() << "signal alloc\n"; AffineExpr d0, d1; // i, j for generated signal dimension bindDims(rewriter.getContext(), d0, d1); @@ -8235,6 +8234,8 @@ struct BeamFormOpLowering : public ConversionPattern { auto pi = rewriter.create( loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.1415926)); + auto zero = rewriter.create(loc, rewriter.getF64Type(), + rewriter.getF64FloatAttr(0)); auto two = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(2)); auto four = rewriter.create(loc, rewriter.getF64Type(), @@ -8242,8 +8243,8 @@ struct BeamFormOpLowering : public ConversionPattern { auto two_pi = rewriter.create(loc, pi, two); // 2 * pi auto freq_val = rewriter.create( loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(frequency)); - auto phase_var = rewriter.create(loc, two_pi, freq_val); - llvm::errs() << "const alloc\n"; + auto phase_var = + rewriter.create(loc, two_pi, freq_val); // 2*pi*freq // for loop from 0 to phase int64_t lb = 0, ub = antennas, step = 1; @@ -8251,7 +8252,6 @@ struct BeamFormOpLowering : public ConversionPattern { rewriter.create(loc, lb, ub, step); auto ivI = forOpI.getInductionVar(); // i : phase rewriter.setInsertionPointToStart(forOpI.getBody()); - llvm::errs() << "first loop\n"; // get the induction var to phase variable auto intType = rewriter.getI64Type(); @@ -8260,27 +8260,29 @@ struct BeamFormOpLowering : public ConversionPattern { auto floatI = rewriter.create(loc, floatType, intI); auto iter_tmp = rewriter.create(loc, floatI, pi); // i * pi - auto iter_args = rewriter.create(loc, iter_tmp, four); + auto iter_args = + rewriter.create(loc, iter_tmp, four); // i*pi/4 // for loop from 0 to timeDim ub = timeDim; affine::AffineForOp forOpJ = rewriter.create(loc, lb, ub, step); - auto ivJ = forOpJ.getInductionVar(); // i : phase + auto ivJ = forOpJ.getInductionVar(); rewriter.setInsertionPointToStart(forOpJ.getBody()); - llvm::errs() << "second loop\n"; + + rewriter.create(loc, zero, alloc, ValueRange{ivJ}); // loop body - auto time_var = - rewriter.create(loc, time, timeMap, ValueRange{ivI, ivJ}); - auto mul_var = rewriter.create(loc, time_var, phase_var); - auto sin_body = rewriter.create(loc, mul_var, iter_args); + auto time_var = rewriter.create( + loc, time, timeMap, ValueRange{ivI, ivJ}); // load from time[j] + auto mul_var = rewriter.create( + loc, time_var, phase_var); // time[j] * (2*pi*freq) + auto sin_body = rewriter.create( + loc, mul_var, iter_args); // time[j] * (2*pi*freq) + i*pi/4 auto result = rewriter.create(loc, sin_body); - llvm::errs() << "body loop\n"; rewriter.create(loc, result, allocSignal, ValueRange{ivI, ivJ}); - forOpJ.dump(); rewriter.setInsertionPointAfter(forOpJ); rewriter.setInsertionPointAfter(forOpI); @@ -8289,14 +8291,12 @@ struct BeamFormOpLowering : public ConversionPattern { rewriter.create(loc, lb, ub, step); auto ivIoutput = forOpIOut.getInductionVar(); rewriter.setInsertionPointToStart(forOpIOut.getBody()); - llvm::errs() << "beam 1 loop\n"; ub = timeDim; affine::AffineForOp forOpJOut = rewriter.create(loc, lb, ub, step); auto ivJoutput = forOpJOut.getInductionVar(); rewriter.setInsertionPointToStart(forOpJOut.getBody()); - llvm::errs() << "beam 2 loop\n"; // load from signal input auto signalInput = rewriter.create( @@ -8305,14 +8305,12 @@ struct BeamFormOpLowering : public ConversionPattern { loc, weights, outputMap, ValueRange{ivIoutput, ivJoutput}); auto intermediateVal = rewriter.create(loc, signalInput, weight); - llvm::errs() << "load from signal input \n"; // load from output auto outputVal = rewriter.create(loc, alloc, ValueRange{ivJoutput}); auto beamOut = rewriter.create(loc, intermediateVal, outputVal); - llvm::errs() << "load from bean output\n"; rewriter.create(loc, beamOut, alloc, ValueRange{ivJoutput}); @@ -8321,7 +8319,6 @@ struct BeamFormOpLowering : public ConversionPattern { rewriter.replaceOp(op, alloc); - llvm::errs() << "success loop\n"; return mlir::success(); } }; diff --git a/mlir/test/Examples/DspExample/dsp_beam_form.py b/mlir/test/Examples/DspExample/dsp_beam_form.py index 64150abc810a..bf4094812185 100644 --- a/mlir/test/Examples/DspExample/dsp_beam_form.py +++ b/mlir/test/Examples/DspExample/dsp_beam_form.py @@ -1,5 +1,6 @@ def main() { - var time = [0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.]; + # var time = [0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.]; + var time = [0.0, 0.25, 0.5, 0.75, 1.0]; var antennas = 4; var freq = 5; var weights = [1,2,3,4]; From d1c6ab4380bf408f1f57be0fc869d3470ce4f98a Mon Sep 17 00:00:00 2001 From: Atharva Khedkar <55466743+AtharvaKhedkar@users.noreply.github.com> Date: Fri, 1 Nov 2024 16:05:34 -0700 Subject: [PATCH 19/45] add signal smoothing (#25) --- .../BenchmarkTest/CCode/signalsmoothing.c | 96 +++++++++++++++++++ .../BenchmarkTest/DSP-DSL/signalsmoothing.py | 6 ++ .../BenchmarkTest/Matlab/signalsmoothing.m | 40 ++++++++ 3 files changed, 142 insertions(+) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/signalsmoothing.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/signalsmoothing.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/signalsmoothing.m diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/signalsmoothing.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/signalsmoothing.c new file mode 100644 index 000000000000..3e7e27aeeddb --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/signalsmoothing.c @@ -0,0 +1,96 @@ +#include +#include +#include // Include math.h for sin() + +#ifndef M_PI + #define M_PI 3.14159265358979323846 // Define Pi if it's not defined +#endif + +// Function to generate an arbitrary noisy signal +void generate_noisy_signal(float *signal, int length, float noise_level) { + for (int i = 0; i < length; i++) { + // Generate a random signal value between -1.0 and 1.0 + float random_value = ((float)rand() / RAND_MAX) * 2.0f - 1.0f; // Random value in [-1, 1] + + // Add random noise + float noise = ((float)rand() / RAND_MAX - 0.5f) * 2.0f * noise_level; // Random noise in [-noise_level, noise_level] + + // Combine random value with noise + signal[i] = random_value + noise; + } +} + +// Function to print the signal +void print_signal(const char *label, float *signal, int length) { + printf("%s:\n", label); + for (int i = 0; i < length; i++) { + printf("%.4f ", signal[i]); // Display 4 digits after the decimal point + } + printf("\n"); +} + +// Function to return the maximum of three numbers +float max_of_three(float a, float b, float c) { + float max = a; + if (b > max) max = b; + if (c > max) max = c; + return max; +} + +// Function to return the minimum of three numbers +float min_of_three(float a, float b, float c) { + float min = a; + if (b < min) min = b; + if (c < min) min = c; + return min; +} + +// Function to apply sliding window average filter with kernel size of 3 +void sliding_avg_filter(float *input, float *output, int length) { + int new_length = length - 3 + 1; + for (int i = 0; i < new_length; i++) { + output[i] = (input[i] + input[i + 1] + input[i + 2]) / 3.0f; + } +} + +// Function to apply sliding window median filter with kernel size of 3 +void sliding_median_filter(float *input, float *output, int length) { + int new_length = length - 3 + 1; + for (int i = 0; i < new_length; i++) { + float a = input[i]; + float b = input[i + 1]; + float c = input[i + 2]; + // Median formula: median = a + b + c - max(a, b, c) - min(a, b, c) + float max_val = max_of_three(a, b, c); + float min_val = min_of_three(a, b, c); + output[i] = a + b + c - max_val - min_val; + } +} + +int main() { + int length = 20; // Length of the original signal + int avg_length = length - 3 + 1; // New length after filtering + int median_length = avg_length - 3 + 1; + + float signal[length]; // Original signal + float avg_filtered[avg_length]; // Signal after average filter + float median_filtered[median_length]; // Signal after median filter + + float noise_level = 0.3f; // Example noise level + + // Generate an arbitrary noisy signal + generate_noisy_signal(signal, length, noise_level); + + // Print the original signal + print_signal("Original Signal", signal, length); + + // Apply sliding window average filter + sliding_avg_filter(signal, avg_filtered, length); + print_signal("After Average Filter", avg_filtered, avg_length); + + // Apply sliding window median filter + sliding_median_filter(avg_filtered, median_filtered, avg_length); + print_signal("After Median Filter", median_filtered, median_length); + + return 0; +} diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/signalsmoothing.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/signalsmoothing.py new file mode 100644 index 000000000000..3af1883cd36c --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/signalsmoothing.py @@ -0,0 +1,6 @@ +def main() { + var input = getRangeOfVector(0, 20, 1); + var average = slidingWindowAvg(input); + var median = slidingWindowAvg(average); + print(median); +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/signalsmoothing.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/signalsmoothing.m new file mode 100644 index 000000000000..7def04cce883 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/signalsmoothing.m @@ -0,0 +1,40 @@ +% Define a function to generate an arbitrary noisy signal +function signal = generate_noisy_signal(length, noise_level) + % Initialize the signal + signal = zeros(1, length); + for i = 1:length + % Generate a random signal value between -1.0 and 1.0 + random_value = rand() * 2 - 1; % Random value in [-1, 1] + + % Add random noise + noise = (rand() - 0.5) * 2 * noise_level; % Random noise in [-noise_level, noise_level] + + % Combine random value with noise + signal(i) = random_value + noise; + end +end + +% Main script +length = 20; % Length of the original signal +noise_level = 0.3; % Example noise level + +% Generate an arbitrary noisy signal +signal = generate_noisy_signal(length, noise_level); + +% Print the original signal +fprintf('Original Signal:\n'); +fprintf('%.4f ', signal); + +% Apply moving average filter with kernel size of 3 +avg_filtered = movmean(signal, 3, 'Endpoints', 'discard'); + +% Print the output after applying the moving average filter +fprintf('After Moving Average Filter:\n'); +fprintf('%.4f ', avg_filtered); + +% Apply moving median filter to the output of the moving average filter +median_filtered = movmedian(avg_filtered, 3, 'Endpoints', 'discard'); + +% Print the output after applying the moving median filter +fprintf('After Moving Median Filter:\n'); +fprintf('%.4f ', median_filtered); From c0579694b2209526515155793b6953bf2746924f Mon Sep 17 00:00:00 2001 From: "Kuo, Mei-Chun" <94007620+Megan0704-1@users.noreply.github.com> Date: Sat, 2 Nov 2024 17:03:38 -0700 Subject: [PATCH 20/45] Space communication application (#24) * ast string support * support string type in dsp mlir * add test case * modulate and demodulate * space communication - w/o decodeing to string * space communication application --- .../BenchmarkTest/CCode/spaceCommunication.c | 129 ++++++++ .../DSP-DSL/spaceCommunication.py | 14 + .../BenchmarkTest/Matlab/spaceCommunication.m | 59 ++++ .../dsp/SimpleBlocks/include/toy/AST.h | 15 + .../dsp/SimpleBlocks/include/toy/Lexer.h | 20 ++ .../dsp/SimpleBlocks/include/toy/Ops.td | 66 ++++ .../dsp/SimpleBlocks/include/toy/Parser.h | 21 +- .../dsp/SimpleBlocks/mlir/Dialect.cpp | 45 +++ .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 311 +++++++++++++----- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 52 +++ mlir/examples/dsp/SimpleBlocks/parser/AST.cpp | 10 +- .../DspExample/dsp_space_communication.py | 10 + mlir/test/Examples/DspExample/dsp_string.py | 10 + 13 files changed, 669 insertions(+), 93 deletions(-) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/spaceCommunication.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/spaceCommunication.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/spaceCommunication.m create mode 100644 mlir/test/Examples/DspExample/dsp_space_communication.py create mode 100644 mlir/test/Examples/DspExample/dsp_string.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/spaceCommunication.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/spaceCommunication.c new file mode 100644 index 000000000000..b59c53c236e6 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/spaceCommunication.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include + +#define INPUT_CHAR_LENGTH 16 // number of characters +#define INPUT_LENGTH INPUT_CHAR_LENGTH*8 // number of bits + +void charToBinary(char c, char *binaryStr) { + for (int i = 7; i >= 0; i--) { + binaryStr[7 - i] = ((c >> i) & 1) ? '1' : '0'; + } + binaryStr[8] = '\0'; // Null-terminate the binary string +} + +// 1. Signal Conditioning (convert to binary) +void condition_signal(const char* data, char* binary) { + binary[0] = '\0'; // Ensure binary string starts empty + for (int i = 0; i < strlen(data); i++) { + char bin[9]; + charToBinary(data[i], bin); + strcat(binary, bin); + } +} + +// 2. Modulation (simple BPSK modulation) +void modulate(const char* binary_data, int* modulated_signal) { + for (int i = 0; i < strlen(binary_data); i++) { + modulated_signal[i] = (binary_data[i] == '1') ? 1 : -1; + } +} + +// 3. Transmission and Reception (add noise to simulate) +void transmit_and_receive(const int* signal, double* received_signal, int length, double noise_level) { + for (int i = 0; i < length; i++) { + double noise = sin(signal[i]); + received_signal[i] = signal[i] + noise; + } +} + +// 4. Demodulation +void demodulate(const double* signal, char* demodulated_data, int length) { + for (int i = 0; i < length; i++) { + demodulated_data[i] = (signal[i] > 0) ? '1' : '0'; + } + demodulated_data[length] = '\0'; // Null-terminate the demodulated data +} + +// 5. Error Correction (simple parity check) +void error_correction(const char* data, char* corrected) { + int length = strlen(data); + int corrected_index = 0; + for (int i = 0; i < length; i += 8) { + int count = 0; + for (int j = 0; j < 8; j++) { + if (data[i + j] == '1') count++; + } + if (count % 2 == 0) { + strncpy(&corrected[corrected_index], &data[i], 8); + } else { + corrected[corrected_index] = '0'; + strncpy(&corrected[corrected_index + 1], &data[i + 1], 7); + } + corrected_index += 8; + } + corrected[corrected_index] = '\0'; +} + +// 6. Data Decoding +void decode_data(const char* binary, char* decoded) { + int length = strlen(binary); + int decoded_index = 0; + for (int i = 0; i < length; i += 8) { + char byte[9]; + strncpy(byte, &binary[i], 8); + byte[8] = '\0'; + decoded[decoded_index++] = (char)strtol(byte, NULL, 2); + } + decoded[decoded_index] = '\0'; // Null-terminate the decoded data +} + +int main() { + const char* space_data = "HELLO FROM SPACE"; + char* binary; + binary = (char*)malloc(INPUT_LENGTH * sizeof(char)); + + for(int i=0; i input.txt +# ./bin/dsp1 ../mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/spaceCommunication.py -emit=jit 2> output.txt +# diff input.txt ouput.txt \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/spaceCommunication.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/spaceCommunication.m new file mode 100644 index 000000000000..9cb245ef8974 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/spaceCommunication.m @@ -0,0 +1,59 @@ +% Parameters +INPUT_CHAR_LENGTH = 16; +INPUT_LENGTH = INPUT_CHAR_LENGTH * 8; + +% Original data +space_data = 'HELLO FROM SPACE'; + +% 1. Signal Conditioning (Convert characters to binary) +binary_data = ''; +for i = 1:length(space_data) + binary_data = strcat(binary_data, dec2bin(space_data(i), 8)); +end + +% 2. Modulation (Simple BPSK Modulation) +modulated_signal = zeros(1, length(binary_data)); +for i = 1:length(binary_data) + if binary_data(i) == '1' + modulated_signal(i) = 1; + else + modulated_signal(i) = -1; + end +end + +% 3. Transmission and Reception (Add noise to simulate) +noise_level = 0.1; +received_signal = modulated_signal + noise_level * randn(1, length(modulated_signal)); + +% 4. Demodulation +demodulated_data = ''; +for i = 1:length(received_signal) + if received_signal(i) > 0 + demodulated_data = strcat(demodulated_data, '1'); + else + demodulated_data = strcat(demodulated_data, '0'); + end +end + +% 5. Error Correction (Simple Parity Check) +corrected_signal = ''; +for i = 1:8:length(demodulated_data) + byte = demodulated_data(i:i+7); + ones_count = sum(byte == '1'); + if mod(ones_count, 2) == 0 + corrected_signal = strcat(corrected_signal, byte); + else + corrected_signal = strcat(corrected_signal, '0', byte(2:end)); + end +end + +% 6. Data Decoding +decoded_data = ''; +for i = 1:8:length(corrected_signal) + byte = corrected_signal(i:i+7); + decoded_data = strcat(decoded_data, char(bin2dec(byte))); +end + +% Display Results +fprintf('Original data: %s\n', space_data); +fprintf('Decoded data: %s\n', decoded_data); diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/AST.h b/mlir/examples/dsp/SimpleBlocks/include/toy/AST.h index c13b287bdd2f..d6b6f2c0e50e 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/AST.h +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/AST.h @@ -43,6 +43,7 @@ class ExprAST { Expr_BinOp, Expr_Call, Expr_Print, + Expr_String, }; ExprAST(ExprASTKind kind, Location location) @@ -107,6 +108,20 @@ class VariableExprAST : public ExprAST { static bool classof(const ExprAST *c) { return c->getKind() == Expr_Var; } }; +/// Expression class for string val. +class StringExprAST : public ExprAST { + std::string string_val; + +public: + StringExprAST(Location loc, llvm::StringRef string_val) + : ExprAST(Expr_String, std::move(loc)), string_val(string_val) {} + + llvm::StringRef getStringVal() { return string_val; } + + /// LLVM style RTTI + static bool classof(const ExprAST *c) { return c->getKind() == Expr_String; } +}; + /// Expression class for defining a variable. class VarDeclExprAST : public ExprAST { std::string name; diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Lexer.h b/mlir/examples/dsp/SimpleBlocks/include/toy/Lexer.h index 38678e23f553..3d5827638470 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Lexer.h +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Lexer.h @@ -44,6 +44,7 @@ enum Token : int { tok_return = -2, tok_var = -3, tok_def = -4, + tok_string_val = -7, // primary tok_identifier = -5, @@ -84,6 +85,11 @@ class Lexer { return identifierStr; } + llvm::StringRef getString() { + assert(curTok == tok_string_val); + return stringVal; + } + /// Return the current number (prereq: getCurToken() == tok_number) double getValue() { assert(curTok == tok_number); @@ -173,6 +179,17 @@ class Lexer { return getTok(); } + // String val: "..." + if(lastChar == '"') { + stringVal = ""; + while (isalnum((lastChar = Token(getNextChar()))) || lastChar == '_' || lastChar== ' ') { + if(lastChar == '"') break; + stringVal += (char)lastChar; + } + lastChar = Token(getNextChar()); + return tok_string_val; + } + // Check for end of file. Don't eat the EOF. if (lastChar == EOF) return tok_eof; @@ -191,6 +208,9 @@ class Lexer { /// If the current Token is an identifier, this string contains the value. std::string identifierStr; + + // If current Token is a string val + std::string stringVal; /// If the current Token is a number, this contains the value. double numVal = 0; diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index f8029b615086..5b0062e32b1e 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -2281,6 +2281,72 @@ def BeamFormOp : Dsp_Op<"beam_form", let hasVerifier = 1; } +//===----------------------------------------------------------------------===// +// SpaceModulateOp +//===----------------------------------------------------------------------===// + +def SpaceModulateOp : Dsp_Op<"space_modulate", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Dsp dialect space modulation operation"; + let description = [{ + Takes in string input and convert it to binary. + }]; + + let arguments = (ins F64Tensor:$signal); + + let results = (outs F64Tensor:$output); + + let builders = [ + OpBuilder<(ins "Value":$signal)> + ]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// SpaceDemodulateOp +//===----------------------------------------------------------------------===// + +def SpaceDemodulateOp : Dsp_Op<"space_demodulate", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Dsp dialect space demodulation operation"; + let description = [{ + Takes in binary input and convert it to string. + }]; + + let arguments = (ins F64Tensor:$binary); + + let results = (outs F64Tensor:$output); + + let builders = [ + OpBuilder<(ins "Value":$binary)> + ]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// SpaceErrCorrectionOp +//===----------------------------------------------------------------------===// + +def SpaceErrCorrectionOp : Dsp_Op<"space_err_correction", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Dsp dialect space error correction operation"; + let description = [{ + Remove noise operation for signal transmission in space. + }]; + + let arguments = (ins F64Tensor:$signal); + + let results = (outs F64Tensor:$output); + + let builders = [ + OpBuilder<(ins "Value":$signal)> + ]; + + let hasVerifier = 1; +} + #endif // TOY_OPS diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Parser.h b/mlir/examples/dsp/SimpleBlocks/include/toy/Parser.h index 42bd653b156c..238f7d441676 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Parser.h +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Parser.h @@ -167,6 +167,16 @@ class Parser { return v; } + /// parenexpr ::= '"' string_val '"' + std::unique_ptr parseStringExpr() { + auto loc = lexer.getLastLocation(); + + std::string string_val(lexer.getString()); + lexer.consume(tok_string_val); + + return std::make_unique(std::move(loc), string_val); + } + /// identifierexpr /// ::= identifier /// ::= identifier '(' expression ')' @@ -175,7 +185,7 @@ class Parser { auto loc = lexer.getLastLocation(); lexer.getNextToken(); // eat identifier. - + if (lexer.getCurToken() != '(') // Simple variable ref. return std::make_unique(std::move(loc), name); @@ -216,6 +226,7 @@ class Parser { /// ::= numberexpr /// ::= parenexpr /// ::= tensorliteral + /// ::= stringexpr std::unique_ptr parsePrimary() { switch (lexer.getCurToken()) { default: @@ -230,6 +241,8 @@ class Parser { return parseParenExpr(); case '[': return parseTensorLiteralExpr(); + case tok_string_val: + return parseStringExpr(); case ';': return nullptr; case '}': @@ -334,7 +347,11 @@ class Parser { if (!type) type = std::make_unique(); lexer.consume(Token('=')); - auto expr = parseExpression(); + std::unique_ptr expr; + if(lexer.getCurToken() == tok_string_val) { + expr = parseStringExpr(); + } + else expr = parseExpression(); return std::make_unique(std::move(loc), std::move(id), std::move(*type), std::move(expr)); } diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index 13327dd6862b..6454dfad5d72 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -2976,6 +2976,51 @@ mlir::LogicalResult BeamFormOp::verify() { return mlir::success(); } +//===----------------------------------------------------------------------===// +// SpaceModulateOp +//===----------------------------------------------------------------------===// + +void SpaceModulateOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, mlir::Value signals) { + state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); + state.addOperands({signals}); +} + +void SpaceModulateOp::inferShapes() { getResult().setType(getSignal().getType()); } + +mlir::LogicalResult SpaceModulateOp::verify() { + return mlir::success(); +} + +//===----------------------------------------------------------------------===// +// SpaceDemodulateOp +//===----------------------------------------------------------------------===// + +void SpaceDemodulateOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, mlir::Value binary) { + state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); + state.addOperands({binary}); +} + +void SpaceDemodulateOp::inferShapes() { getResult().setType(getBinary().getType()); } + +mlir::LogicalResult SpaceDemodulateOp::verify() { + return mlir::success(); +} + +//===----------------------------------------------------------------------===// +// SpaceDemodulateOp +//===----------------------------------------------------------------------===// + +void SpaceErrCorrectionOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, mlir::Value signal) { + state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); + state.addOperands({signal}); +} + +void SpaceErrCorrectionOp::inferShapes() { getResult().setType(getSignal().getType()); } + +mlir::LogicalResult SpaceErrCorrectionOp::verify() { + return mlir::success(); +} + //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index cdcc91133206..87a8ad6c10ad 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -8022,10 +8022,10 @@ struct QamModulateRealOpLowering : public ConversionPattern { Value out = rewriter.create(loc, zeroReal, negOneVal, oneVal); - rewriter.create(loc, out, alloc, outputRealMap, - ValueRange{ivI}); - rewriter.setInsertionPointAfter(forOpI); - rewriter.replaceOp(op, alloc); + rewriter.create(loc, out, alloc, outputRealMap, ValueRange{ivI}); + + rewriter.setInsertionPointAfter(forOpI); + rewriter.replaceOp(op, alloc); return success(); } @@ -8175,12 +8175,14 @@ struct QamDemodulateOpLowering : public ConversionPattern { rewriter.create(loc, out2, alloc, outputMapImagine, ValueRange{ivI}); - rewriter.replaceOp(op, alloc); + rewriter.setInsertionPointAfter(forOpI); + rewriter.replaceOp(op, alloc); return success(); } }; // qam_demodulate op +//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: BeamForm operations //===----------------------------------------------------------------------===// @@ -8195,11 +8197,10 @@ struct BeamFormOpLowering : public ConversionPattern { auto loc = op->getLoc(); auto beamFormOp = llvm::dyn_cast(op); - // allocating space for output - auto output = llvm::dyn_cast((*op->result_type_begin())); - llvm::errs() << "output case.\n"; - auto outputMemRefType = convertTensorToMemRef(output); - auto alloc = insertAllocAndDealloc(outputMemRefType, loc, rewriter); + // allocating space for output + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMemRefType = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMemRefType, loc, rewriter); BeamFormOpAdaptor beamFormAdaptor(operands); auto time = beamFormAdaptor.getTime(); @@ -8210,14 +8211,14 @@ struct BeamFormOpLowering : public ConversionPattern { int64_t antennas = beamFormOp.getAntennas(); int64_t frequency = beamFormOp.getFreq(); - llvm::ArrayRef signalShape{antennas, timeDim}; - auto signalType = output.clone(signalShape); - - auto signalMemRefType = convertTensorToMemRef(signalType); - auto allocSignal = insertAllocAndDealloc(signalMemRefType, loc, rewriter); - - AffineExpr d0, d1; // i, j for generated signal dimension - bindDims(rewriter.getContext(), d0, d1); + llvm::ArrayRef signalShape{antennas, timeDim}; + auto signalType = output.clone(signalShape); + + auto signalMemRefType = convertTensorToMemRef(signalType); + auto allocSignal = insertAllocAndDealloc(signalMemRefType, loc, rewriter); + + AffineExpr d0, d1; // i, j for generated signal dimension + bindDims(rewriter.getContext(), d0, d1); // generated input map AffineMap genInputMap = @@ -8232,26 +8233,18 @@ struct BeamFormOpLowering : public ConversionPattern { AffineMap outputMap = AffineMap::get(2, 0, ArrayRef{d0}, rewriter.getContext()); - auto pi = rewriter.create( - loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.1415926)); - auto zero = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(0)); - auto two = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(2)); - auto four = rewriter.create(loc, rewriter.getF64Type(), - rewriter.getF64FloatAttr(4)); - auto two_pi = rewriter.create(loc, pi, two); // 2 * pi - auto freq_val = rewriter.create( - loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(frequency)); - auto phase_var = - rewriter.create(loc, two_pi, freq_val); // 2*pi*freq - - // for loop from 0 to phase - int64_t lb = 0, ub = antennas, step = 1; - affine::AffineForOp forOpI = - rewriter.create(loc, lb, ub, step); - auto ivI = forOpI.getInductionVar(); // i : phase - rewriter.setInsertionPointToStart(forOpI.getBody()); + auto pi = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.1415926)); + auto two = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(2)); + auto four = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(4)); + auto two_pi = rewriter.create(loc, pi, two); // 2 * pi + auto freq_val = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(frequency)); + auto phase_var = rewriter.create(loc, two_pi, freq_val); + + // for loop from 0 to phase + int64_t lb = 0, ub = antennas, step=1; + affine::AffineForOp forOpI = rewriter.create(loc, lb, ub, step); + auto ivI = forOpI.getInductionVar(); // i : phase + rewriter.setInsertionPointToStart(forOpI.getBody()); // get the induction var to phase variable auto intType = rewriter.getI64Type(); @@ -8263,67 +8256,205 @@ struct BeamFormOpLowering : public ConversionPattern { auto iter_args = rewriter.create(loc, iter_tmp, four); // i*pi/4 - // for loop from 0 to timeDim - ub = timeDim; - affine::AffineForOp forOpJ = - rewriter.create(loc, lb, ub, step); - auto ivJ = forOpJ.getInductionVar(); - rewriter.setInsertionPointToStart(forOpJ.getBody()); + // for loop from 0 to timeDim + ub = timeDim; + affine::AffineForOp forOpJ = rewriter.create(loc, lb, ub, step); + auto ivJ = forOpJ.getInductionVar(); // i : phase + rewriter.setInsertionPointToStart(forOpJ.getBody()); + + // loop body + auto time_var = rewriter.create(loc, time, timeMap, ValueRange{ivI, ivJ}); + auto mul_var = rewriter.create(loc, time_var, phase_var); + auto sin_body = rewriter.create(loc, mul_var, iter_args); + auto result = rewriter.create(loc, sin_body); + rewriter.create(loc, result, allocSignal, ValueRange{ivI, ivJ}); + + rewriter.setInsertionPointAfter(forOpJ); + rewriter.setInsertionPointAfter(forOpI); + + ub = antennas; + affine::AffineForOp forOpIOut = rewriter.create(loc, lb, ub, step); + auto ivIoutput = forOpIOut.getInductionVar(); + rewriter.setInsertionPointToStart(forOpIOut.getBody()); + + ub = timeDim; + affine::AffineForOp forOpJOut = rewriter.create(loc, lb, ub, step); + auto ivJoutput = forOpJOut.getInductionVar(); + rewriter.setInsertionPointToStart(forOpJOut.getBody()); + + // load from signal input + auto signalInput = rewriter.create(loc, allocSignal, genInputMap, ValueRange{ivIoutput, ivJoutput}); + auto weight = rewriter.create(loc, weights, outputMap, ValueRange{ivIoutput, ivJoutput}); + auto intermediateVal = rewriter.create(loc, signalInput, weight); + + // load from output + auto outputVal = rewriter.create(loc, alloc, ValueRange{ivJoutput}); + auto beamOut = rewriter.create(loc, intermediateVal, outputVal); + + rewriter.create(loc, beamOut, alloc, ValueRange{ivJoutput}); - rewriter.create(loc, zero, alloc, ValueRange{ivJ}); + rewriter.setInsertionPointAfter(forOpJOut); + rewriter.setInsertionPointAfter(forOpIOut); - // loop body - auto time_var = rewriter.create( - loc, time, timeMap, ValueRange{ivI, ivJ}); // load from time[j] - auto mul_var = rewriter.create( - loc, time_var, phase_var); // time[j] * (2*pi*freq) - auto sin_body = rewriter.create( - loc, mul_var, iter_args); // time[j] * (2*pi*freq) + i*pi/4 - auto result = rewriter.create(loc, sin_body); - rewriter.create(loc, result, allocSignal, - ValueRange{ivI, ivJ}); + rewriter.replaceOp(op, alloc); + + return mlir::success(); - rewriter.setInsertionPointAfter(forOpJ); - rewriter.setInsertionPointAfter(forOpI); + } +}; - ub = antennas; - affine::AffineForOp forOpIOut = - rewriter.create(loc, lb, ub, step); - auto ivIoutput = forOpIOut.getInductionVar(); - rewriter.setInsertionPointToStart(forOpIOut.getBody()); +struct SpaceModulateOpLowering : public ConversionPattern { + SpaceModulateOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::SpaceModulateOp::getOperationName(), 1, ctx) {} + + mlir::LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); - ub = timeDim; - affine::AffineForOp forOpJOut = - rewriter.create(loc, lb, ub, step); - auto ivJoutput = forOpJOut.getInductionVar(); - rewriter.setInsertionPointToStart(forOpJOut.getBody()); + // output + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); - // load from signal input - auto signalInput = rewriter.create( - loc, allocSignal, genInputMap, ValueRange{ivIoutput, ivJoutput}); - auto weight = rewriter.create( - loc, weights, outputMap, ValueRange{ivIoutput, ivJoutput}); - auto intermediateVal = - rewriter.create(loc, signalInput, weight); + SpaceModulateOpAdaptor spaceModAdaptor(operands); + Value signal = spaceModAdaptor.getSignal(); + auto signalType = llvm::dyn_cast(op->getOperand(0).getType()); + llvm::ArrayRef signalShape = signalType.getShape(); + + Value negOneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value oneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + + // one dim loop + int64_t lb=0, ub=signalShape[0], step=1; + AffineForOp forOp = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOp.getBody()); + auto iv = forOp.getInductionVar(); + + Value bit = rewriter.create(loc, signal, ValueRange{iv}); + + Value isOne = rewriter.create(loc, arith::CmpFPredicate::OEQ, bit, oneVal); + + auto out = rewriter.create(loc, isOne, oneVal, negOneVal); + + rewriter.create(loc, out, alloc, ValueRange{iv}); + rewriter.setInsertionPointAfter(forOp); - // load from output - auto outputVal = - rewriter.create(loc, alloc, ValueRange{ivJoutput}); - auto beamOut = - rewriter.create(loc, intermediateVal, outputVal); + rewriter.replaceOp(op, alloc); + return mlir::success(); + } +}; // space modulate - rewriter.create(loc, beamOut, alloc, ValueRange{ivJoutput}); +struct SpaceDemodulateOpLowering : public ConversionPattern { + SpaceDemodulateOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::SpaceDemodulateOp::getOperationName(), 1, ctx) {} - rewriter.setInsertionPointAfter(forOpJOut); - rewriter.setInsertionPointAfter(forOpIOut); + mlir::LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); - rewriter.replaceOp(op, alloc); + // output + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); - return mlir::success(); - } -}; + SpaceDemodulateOpAdaptor spaceDemodAdaptor(operands); + Value binary = spaceDemodAdaptor.getBinary(); + auto binaryType = llvm::dyn_cast(op->getOperand(0).getType()); + llvm::ArrayRef binaryShape = binaryType.getShape(); -} // namespace + Value negOneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value oneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + + // one dim loop + int64_t lb=0, ub=binaryShape[0], step=1; + AffineForOp forOp = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOp.getBody()); + auto iv = forOp.getInductionVar(); + + Value bit = rewriter.create(loc, binary, ValueRange{iv}); + + Value isOne = rewriter.create(loc, arith::CmpFPredicate::OGE, bit, oneVal); + + auto out = rewriter.create(loc, isOne, oneVal, zeroVal); + + rewriter.create(loc, out, alloc, ValueRange{iv}); + + rewriter.setInsertionPointAfter(forOp); + rewriter.replaceOp(op, alloc); + return mlir::success(); + } +}; // soace demodulate + +struct SpaceErrCorrectionOpLowering : public ConversionPattern { + SpaceErrCorrectionOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::SpaceErrCorrectionOp::getOperationName(), 1, ctx) {} + + mlir::LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + // output + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); + + SpaceErrCorrectionOpAdaptor adaptor(operands); + Value signal = adaptor.getSignal(); + auto signalType = llvm::dyn_cast(op->getOperand(0).getType()); + llvm::ArrayRef signalShape = signalType.getShape(); + + Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value oneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + Value twoVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(2)); + + AffineExpr d0, d1; + bindDims(rewriter.getContext(), d0, d1); + AffineMap first = AffineMap::get(2, 0, ArrayRef{d0}, rewriter.getContext()); + AffineMap index = AffineMap::get(2, 0, ArrayRef{d0+d1}, rewriter.getContext()); + + int64_t lb=0, ub=signalShape[0], step=8; + AffineForOp forOpI = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOpI.getBody()); + auto ivI = forOpI.getInductionVar(); + + auto firstVal = rewriter.create(loc, signal, ValueRange{ivI}); // signal [0] + rewriter.create(loc, firstVal, alloc, ValueRange{ivI}); // store signal[0] to alloc[0] + + int64_t inner_lb=1, inner_ub=8, inner_step=1; + AffineForOp forOpJ = rewriter.create(loc, inner_lb, inner_ub, inner_step); + rewriter.setInsertionPointToStart(forOpJ.getBody()); + auto ivJ = forOpJ.getInductionVar(); + + auto stored = rewriter.create(loc, alloc, first, ValueRange{ivI, ivJ}); // load alloc[0] + auto loaded = rewriter.create(loc, signal, index, ValueRange{ivI, ivJ}); // load signal[1...7] + + auto added = rewriter.create(loc, stored, loaded); // add + rewriter.create(loc, added, alloc, ValueRange{ivI}); // store val to alloc[0] + rewriter.create(loc, loaded, alloc, index, ValueRange{ivI, ivJ}); // store val to alloc[1...7] + + rewriter.setInsertionPointAfter(forOpJ); + + auto initVal = rewriter.create(loc, signal, ValueRange{ivI}); // load signal[0] + auto oneCount = rewriter.create(loc, alloc, ValueRange{ivI}); // load alloc[0] + auto parityCheck = rewriter.create(loc, oneCount, twoVal); // get remainder from oneCount / 2 -> either 1 or 0 + + auto oddParity = rewriter.create(loc, arith::CmpFPredicate::OEQ, oneVal, parityCheck); // if paritycheck == 1 + auto valToAlloc = rewriter.create(loc, oddParity, zeroVal, initVal); // if true: valToAlloc = 0 else NC + + rewriter.create(loc, valToAlloc, alloc, ValueRange{ivI}); // store the value to alloc[0] + + rewriter.setInsertionPointAfter(forOpI); + + rewriter.replaceOp(op, alloc); + return mlir::success(); + } +}; +}// namespace //===----------------------------------------------------------------------===// // ToyToAffineLoweringPass @@ -8395,9 +8526,9 @@ void ToyToAffineLoweringPass::runOnOperation() { FIRFilterYSymmOptimizedOpLowering, FFT1DRealSymmOpLowering, FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering, ShiftRightOpLowering, MatmulOpLowering, - ThresholdUpOpLowering, QamModulateRealOpLowering, - QamModulateImgOpLowering, QamDemodulateOpLowering, FindPeaksOpLowering, - BeamFormOpLowering, MedianFilterOpLowering>(&getContext()); + ThresholdUpOpLowering, QamModulateRealOpLowering, QamModulateImgOpLowering, + QamDemodulateOpLowering, FindPeaksOpLowering, BeamFormOpLowering, + SpaceModulateOpLowering, SpaceDemodulateOpLowering, SpaceErrCorrectionOpLowering>(&getContext()); // With the target and rewrite patterns defined, we can now attempt the // conversion. The conversion will signal failure if any of our `illegal` diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index 504d203524c2..6ad8054172b1 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -39,6 +39,7 @@ #include #include #include +#include using namespace mlir::dsp; using namespace dsp; @@ -923,6 +924,33 @@ class MLIRGenImpl { return nullptr; } return builder.create(location, operands[0], operands[1]); + } + // space_demodulate + if(callee == "space_demodulate") { + if(call.getArgs().size() != 1) { + emitError(location, "MLIR codegen encountered an error: dsp.SpaceDemodulateOp" + "accepts 1 arguments"); + return nullptr; + } + return builder.create(location, operands[0]); + } + // space_modulate + if(callee == "space_modulate") { + if(call.getArgs().size() != 1) { + emitError(location, "MLIR codegen encountered an error: dsp.SpaceModulateOp" + "accepts 1 arguments"); + return nullptr; + } + return builder.create(location, operands[0]); + } + // space_err_correction + if(callee == "space_err_correction") { + if(call.getArgs().size() != 1) { + emitError(location, "MLIR codegen encountered an error: dsp.SpaceErrCorrectionOp" + "accepts 1 arguments"); + return nullptr; + } + return builder.create(location, operands[0]); } // Builtin calls have their custom operation, meaning this is a // straightforward emission. @@ -956,6 +984,28 @@ class MLIRGenImpl { mlir::Value mlirGen(NumberExprAST &num) { return builder.create(loc(num.loc()), num.getValue()); } + + /// Emit a string exression + mlir::Value mlirGen(StringExprAST &expr) { + auto string_val = expr.getStringVal(); + + std::vector signals; + for(char ch : string_val) { + std::bitset<8> bits(static_cast(ch)), reversed; + int n = 8; + for(int i=0; i(loc(expr.loc()), type, dataAttr); + } /// Dispatch codegen for the right expression subclass using RTTI. mlir::Value mlirGen(ExprAST &expr) { @@ -970,6 +1020,8 @@ class MLIRGenImpl { return mlirGen(cast(expr)); case dsp::ExprAST::Expr_Num: return mlirGen(cast(expr)); + case dsp::ExprAST::Expr_String: + return mlirGen(cast(expr)); default: emitError(loc(expr.loc())) << "MLIR codegen encountered an unhandled expr kind '" diff --git a/mlir/examples/dsp/SimpleBlocks/parser/AST.cpp b/mlir/examples/dsp/SimpleBlocks/parser/AST.cpp index a5dfa2d0f16e..6b824aa59997 100644 --- a/mlir/examples/dsp/SimpleBlocks/parser/AST.cpp +++ b/mlir/examples/dsp/SimpleBlocks/parser/AST.cpp @@ -51,6 +51,7 @@ class ASTDumper { void dump(PrintExprAST *node); void dump(PrototypeAST *node); void dump(FunctionAST *node); + void dump(StringExprAST *node); // Actually print spaces matching the current indentation level void indent() { @@ -81,7 +82,7 @@ static std::string loc(T *node) { void ASTDumper::dump(ExprAST *expr) { llvm::TypeSwitch(expr) .Case( + PrintExprAST, ReturnExprAST, VarDeclExprAST, StringExprAST, VariableExprAST>( [&](auto *node) { this->dump(node); }) .Default([&](ExprAST *) { // No match, fallback to a generic message @@ -90,6 +91,13 @@ void ASTDumper::dump(ExprAST *expr) { }); } +/// A string expression +void ASTDumper::dump(StringExprAST *stringExpr) { + INDENT(); + llvm::errs() << "StringExpr \"" << stringExpr->getStringVal() << "\""; + llvm::errs() << " " << loc(stringExpr) << "\n"; +} + /// A variable declaration is printing the variable name, the type, and then /// recurse in the initializer value. void ASTDumper::dump(VarDeclExprAST *varDecl) { diff --git a/mlir/test/Examples/DspExample/dsp_space_communication.py b/mlir/test/Examples/DspExample/dsp_space_communication.py new file mode 100644 index 000000000000..2cb2227d3733 --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_space_communication.py @@ -0,0 +1,10 @@ +def main() { + var d = "HELLO FROM SPACE"; + # print(d); + var a = space_modulate(d); + var noise = sin(a); + var noisy_signal = a+noise; + var b = space_demodulate(noisy_signal); + var e = space_err_correction(d); + print(e); +} diff --git a/mlir/test/Examples/DspExample/dsp_string.py b/mlir/test/Examples/DspExample/dsp_string.py new file mode 100644 index 000000000000..ead95cf07e73 --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_string.py @@ -0,0 +1,10 @@ +def main() { + var a = [[[10,20],[30,0]] ]; + var b = [[[40,50],[60,70]] ]; + var c = sub(a, b); + print(c); + + var d = "HELLO FROM SPACE"; + print(d); + print("abd"); +} From ad08969e76aec6ee8379fa5bdb1621fe52a3f73a Mon Sep 17 00:00:00 2001 From: HwisooSo Date: Sun, 3 Nov 2024 00:26:41 -0700 Subject: [PATCH 21/45] add Findpeaks, biomed signal processing with canonOpt (#26) Co-authored-by: Atharva Khedkar --- .../dsp/SimpleBlocks/include/toy/Ops.td | 140 +++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 146 +++- .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 819 +++++++++++++----- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 244 ++++-- .../dsp/SimpleBlocks/mlir/ToyCombine.cpp | 630 ++++++++------ .../Examples/DspExample/dsp_biomedical.py | 64 ++ 6 files changed, 1469 insertions(+), 574 deletions(-) create mode 100644 mlir/test/Examples/DspExample/dsp_biomedical.py diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 5b0062e32b1e..d6591348235e 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -1336,6 +1336,53 @@ def GetElemAtIndxOp : Dsp_Op<"getElemAtIndx", } + + + + +//===----------------------------------------------------------------------===// +// GetSingleElemAtIdxOp +//===----------------------------------------------------------------------===// + +def GetSingleElemAtIdxOp : Dsp_Op<"getSingleElemAtIndx", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "This one access ranked tensor at idx and returns signle tensor without dimension."; + + let arguments = (ins F64Tensor:$input, F64Tensor:$indx); + let results = (outs F64Tensor); + + let builders = [ + OpBuilder<(ins "Value":$input, "Value":$indx)> + ]; + + // Indicate that additional verification for this operation is necessary. + //let hasVerifier = 1; +} + + + + +//===----------------------------------------------------------------------===// +// Diff2MeanOptimizedOp +//===----------------------------------------------------------------------===// + +def Diff2MeanOptimizedOp : Dsp_Op<"diff2meanOpt", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "This one implemnets mean(diff(input)) as (input[-1] - input[0])/len(input). Note that mean uses length of diff, this operation consider input[-1] == input[length paramter of mean], not input[length paramter of mean-1]"; + + let arguments = (ins F64Tensor:$input, F64Tensor:$length); + let results = (outs F64Tensor); + + let builders = [ + OpBuilder<(ins "Value":$input, "Value":$length)> + ]; +} + + + + + + //===----------------------------------------------------------------------===// // SetElemAtIndxOp //===----------------------------------------------------------------------===// @@ -2347,6 +2394,99 @@ def SpaceErrCorrectionOp : Dsp_Op<"space_err_correction", let hasVerifier = 1; } + +//===----------------------------------------------------------------------===// +// MaxOp +//===----------------------------------------------------------------------===// + +def MaxOp : Dsp_Op<"max", [Pure , DeclareOpInterfaceMethods]> { + let summary = "Find maximum value in tensor"; + let description = [{ + This operation finds and returns the maximum value of the tensor. + }]; + + let arguments = (ins F64Tensor:$input); + let results = (outs F64Tensor); + + // Indicate that the operation has a custom parser and printer method. + // let hasCustomAssemblyFormat = 1; + // let assemblyFormat = [{ + // `(` $input `:` type($input1 , $input2) `)` attr-dict `to` type(results) + // }]; + // Allow building a MulOp with from the two input operands. + + let builders = [ + OpBuilder<(ins "Value":$input)> + ]; + + // Indicate that the operation has a custom parser and printer method. + // let hasCustomAssemblyFormat = 1; + + // let hasVerifier = 1; + } + + +//===----------------------------------------------------------------------===// +// MeanOp +//===----------------------------------------------------------------------===// + +def MeanOp : Dsp_Op<"mean", [Pure , DeclareOpInterfaceMethods]> { + let summary = "Find mean value of tensor. Requires (input, length)."; + let description = [{ + This operation finds and returns the mean value of the tensor. + Note that it requires length. + It would be better if we can implement both versions + - no length argument -> automatically use the length of tensor + - with length argument -> use the provided length + }]; + + let arguments = (ins F64Tensor:$input, F64Tensor:$length); + let results = (outs F64Tensor); + + let builders = [ + OpBuilder<(ins "Value":$input, "Value":$length)> + ]; + + // Indicate that the operation has a custom parser and printer method. + // let hasCustomAssemblyFormat = 1; + + // let hasVerifier = 1; + let hasCanonicalizer = 1; + } + + + +//===----------------------------------------------------------------------===// +// DiffOp +//===----------------------------------------------------------------------===// + +def DiffOp : Dsp_Op<"diff", [Pure , DeclareOpInterfaceMethods]> { + let summary = "np.diff (out[i] = a[i+1] - a[i]). It receives second argument as length"; + let description = [{ + This operation returns a tensor that contains diff (out[i] = a[i+1] - a[i]). + The length of the output tensor is len(input)-1, regardless of length parameter. + Note that it requires length. + It would be better if we can implement both versions + - no length argument -> automatically use the length of tensor + - with length argument -> use the provided length + }]; + + let arguments = (ins F64Tensor:$input, F64Tensor:$length); + let results = (outs F64Tensor); + + let builders = [ + OpBuilder<(ins "Value":$input, "Value":$length)> + ]; + + // Indicate that the operation has a custom parser and printer method. + // let hasCustomAssemblyFormat = 1; + + // let hasVerifier = 1; + } + + + + #endif // TOY_OPS diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index 6454dfad5d72..ec5cc64994b9 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -697,15 +697,111 @@ mlir::LogicalResult MatmulOp::verify() { state.addOperands({signal, height, distance}); } - - /// Infer the output shape of the FindPeaksOp, this is required by the shape inference - /// interface. void FindPeaksOp::inferShapes() { - getResult().setType(getSignal().getType()); + // Maximum possible number of peaks = (length of signal -1) / distance + 1. + // We will return a tensor with size (length of signal -1) / distance + 1 + 1(last one to provide number of peaks). + auto signalType = getSignal().getType(); + auto signalShape = signalType.getShape(); + int64_t len_signal = signalShape[0]; + + Value distanceArg = getOperand(2); + dsp::ConstantOp constantOpDistance = + distanceArg.getDefiningOp(); + DenseElementsAttr constantDistanceValue = constantOpDistance.getValue(); + + auto elements = constantDistanceValue.getValues(); + float distanceFloat = elements[0].getValueAsDouble(); + //SecondValueInt = (int64_t)SecondValue; + + int64_t sizeOfOutput = (len_signal-1)/distanceFloat + 2; + + std::vector shapeForOutput; + shapeForOutput.push_back(sizeOfOutput); + + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, signalType.getElementType()); + + getResult().setType(manipulatedType); + } +//===----------------------------------------------------------------------===// + // MaxOp + //===----------------------------------------------------------------------===// + + void MaxOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value input) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({input}); + } + + /// Infer the output shape of the MaxOp, this is required by the shape inference + /// interface. + void MaxOp::inferShapes() { + auto tensorInput = getInput().getType(); + //auto shapeOfInput = tensorInput.getShape(); + + std::vector shapeForOutput; + + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, tensorInput.getElementType()); + + getResult().setType(manipulatedType); + +} + + +//===----------------------------------------------------------------------===// + // MeanOp + //===----------------------------------------------------------------------===// + + void MeanOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value input, mlir::Value length) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({input, length}); + } + + void MeanOp::inferShapes() { + auto tensorInput = getInput().getType(); + + std::vector shapeForOutput; + + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, tensorInput.getElementType()); + + getResult().setType(manipulatedType); +} + + +//===----------------------------------------------------------------------===// + // DiffOp + //===----------------------------------------------------------------------===// + + void DiffOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value input, mlir::Value length) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({input, length}); + } + + void DiffOp::inferShapes() { + auto tensorInput = getInput().getType(); + auto shapeOfInput = tensorInput.getShape(); + + std::vector shapeForOutput; + shapeForOutput.push_back(shapeOfInput[0]-1); + + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, tensorInput.getElementType()); + + getResult().setType(manipulatedType); +} + + + + + //===----------------------------------------------------------------------===// @@ -1671,6 +1767,48 @@ mlir::LogicalResult GetElemAtIndxOp::verify() { return mlir::success(); } + + + +//===----------------------------------------------------------------------===// +// GetSingleElemAtIdxOp +//===----------------------------------------------------------------------===// + +void GetSingleElemAtIdxOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value input, + mlir::Value indx) { + state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); + state.addOperands({input, indx}); +} + +void GetSingleElemAtIdxOp::inferShapes() { + std::vector shapeForOutput; + + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, getInput().getType().getElementType()); + getResult().setType(manipulatedType); +} + +//===----------------------------------------------------------------------===// +// Diff2MeanOptimizedOp +//===----------------------------------------------------------------------===// + +void Diff2MeanOptimizedOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value input, + mlir::Value length) { + state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); + state.addOperands({input, length}); +} + +void Diff2MeanOptimizedOp::inferShapes() { + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + {}, getInput().getType().getElementType()); + getResult().setType(manipulatedType); + +} + + + //===----------------------------------------------------------------------===// // SetElemAtIndxOp //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 87a8ad6c10ad..6dfce74c6ec2 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -6921,7 +6921,7 @@ struct zeroCrossCountOpLowering : public ConversionPattern { Type integerType = rewriter.getI64Type(); // allocation & deallocation for the result of this operation - auto memRefType = convertTensorToMemRef(tensorType); + // auto memRefType = convertTensorToMemRef(tensorType); // Force the result to be a tensor of size 1 auto alloc = insertAllocAndDealloc( MemRefType::get(ArrayRef(1), tensorType.getElementType()), loc, @@ -7280,19 +7280,39 @@ struct FindPeaksOpLowering : public ConversionPattern { rewriter.create(loc, constant_index_zero, alloc_peaks_count, ValueRange{}); + auto heightArgType = + llvm::dyn_cast(op->getOperand(1).getType()); + + int heightArgShape = heightArgType.getShape().size(); + + ValueRange heightValueRange; + + if (heightArgShape == 0) + heightValueRange = ValueRange{}; + else + heightValueRange = ValueRange{constant_index_zero}; + + auto distanceArgType = + llvm::dyn_cast(op->getOperand(2).getType()); + + int distanceArgShape = distanceArgType.getShape().size(); + + ValueRange distanceValueRange; + + if (distanceArgShape == 0) + distanceValueRange = ValueRange{}; + else + distanceValueRange = ValueRange{constant_index_zero}; + auto signalType = llvm::dyn_cast(op->getOperand(0).getType()); int64_t lb = 1; int64_t ub = signalType.getShape()[0] - 1; int64_t step = 1; - Value constant_len_singal = rewriter.create( - loc, rewriter.getIndexType(), - rewriter.getIndexAttr(signalType.getShape()[0])); - //%distance = affine.load %alloc_distance[] : memref auto distance_fp = rewriter.create( - loc, findPeaksOpAdaptor.getDistance(), ValueRange{}); + loc, findPeaksOpAdaptor.getDistance(), distanceValueRange); // f64 to index Value distance_ui = rewriter.create( loc, rewriter.getIntegerType(32), distance_fp); @@ -7300,7 +7320,7 @@ struct FindPeaksOpLowering : public ConversionPattern { loc, rewriter.getIndexType(), distance_ui); affine::AffineForOp forOpInit = - rewriter.create(loc, lb, ub, step); + rewriter.create(loc, 0, tensorType.getShape()[0], step); auto init_iter = forOpInit.getInductionVar(); rewriter.setInsertionPointToStart(forOpInit.getBody()); @@ -7333,12 +7353,12 @@ struct FindPeaksOpLowering : public ConversionPattern { rewriter.create(loc, findPeaksOpAdaptor.getSignal(), addMapForPrev, ValueRange{current_index}); auto signal_current = rewriter.create( - loc, findPeaksOpAdaptor.getSignal(), current_index); + loc, findPeaksOpAdaptor.getSignal(), ValueRange{current_index}); auto signal_next = rewriter.create(loc, findPeaksOpAdaptor.getSignal(), addMapForNext, ValueRange{current_index}); auto height = rewriter.create( - loc, findPeaksOpAdaptor.getHeight(), ValueRange{}); + loc, findPeaksOpAdaptor.getHeight(), heightValueRange); //%cmp_current_prev = arith.cmpf ogt, %signal_current, %signal_prev : f64 //%cmp_current_next = arith.cmpf ogt, %signal_current, %signal_next : f64 @@ -7452,9 +7472,351 @@ struct FindPeaksOpLowering : public ConversionPattern { loc, rewriter.getIntegerType(32), peaks_count_final); Value peaks_count_final_to_f64 = rewriter.create( loc, rewriter.getF64Type(), peaks_count_final_to_ui); + + Value result_size = rewriter.create( + loc, rewriter.getIndexType(), + rewriter.getIndexAttr(tensorType.getShape()[0])); + Value result_size_minusOne = + rewriter.create(loc, result_size, constant_index_one); rewriter.create(loc, peaks_count_final_to_f64, alloc_output, - addMapForPrev, - ValueRange{constant_len_singal}); + ValueRange{result_size_minusOne}); + + rewriter.replaceOp(op, alloc_output); + + return success(); + } +}; + +struct MaxOpLowering : public ConversionPattern { + MaxOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::MaxOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + + // Get the location of GainOp + auto loc = op->getLoc(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation + auto memRefType = convertTensorToMemRef(tensorType); + auto alloc_output = insertAllocAndDealloc(memRefType, loc, rewriter); + + typename dsp::MaxOp::Adaptor maxOpAdaptor(operands); + + Value constantZero = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + + // Value cst_idx_zero = rewriter.create(loc, 0); + + rewriter.create(loc, constantZero, alloc_output, + ValueRange{}); + + auto inputType = + llvm::dyn_cast(op->getOperand(0).getType()); + + // loop for 0 <= i < N + int64_t lb = 0; + int64_t ub = inputType.getShape()[0]; + int64_t step = 1; + + affine::AffineForOp forOp = rewriter.create(loc, lb, ub, step); + auto idx = forOp.getInductionVar(); + rewriter.setInsertionPointToStart(forOp.getBody()); + + auto loadedInput = rewriter.create( + loc, maxOpAdaptor.getInput(), ValueRange{idx}); + auto loadedOutput = + rewriter.create(loc, alloc_output, ValueRange{}); + auto compare_input_output = rewriter.create( + loc, arith::CmpFPredicate::OGT, loadedInput, loadedOutput); + + auto ifOp = rewriter.create(loc, compare_input_output, false); + + rewriter.setInsertionPointToStart(ifOp.thenBlock()); + + rewriter.create(loc, loadedInput, alloc_output, + ValueRange{}); + + rewriter.setInsertionPointAfter(forOp); + + rewriter.replaceOp(op, alloc_output); + + return success(); + } +}; + +struct MeanOpLowering : public ConversionPattern { + MeanOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::MeanOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + + // Get the location of GainOp + auto loc = op->getLoc(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation + auto memRefType = convertTensorToMemRef(tensorType); + auto alloc_output = insertAllocAndDealloc(memRefType, loc, rewriter); + + typename dsp::MeanOp::Adaptor meanOpAdaptor(operands); + + Value constantZero = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value cst_idx_zero = rewriter.create(loc, 0); + + rewriter.create(loc, constantZero, alloc_output, + ValueRange{}); + + auto lengthArgType = + llvm::dyn_cast(op->getOperand(1).getType()); + + int lengthArgShape = lengthArgType.getShape().size(); + + ValueRange lengthValueRange; + + if (lengthArgShape == 0) + lengthValueRange = ValueRange{}; + else + lengthValueRange = ValueRange{cst_idx_zero}; + + auto loadedLength = rewriter.create( + loc, meanOpAdaptor.getLength(), lengthValueRange); + + // f64 to index + Value length_ui = rewriter.create( + loc, rewriter.getIntegerType(32), loadedLength); + Value length_index = rewriter.create( + loc, rewriter.getIndexType(), length_ui); + + // loop for 0 <= i < length + // Note: we need to use scf.for and memref::LoadOp/StoreOp (can we use + // dynamic ub for affine.for?) + auto lb = rewriter.create(loc, 0); + auto step = rewriter.create(loc, 1); + auto forOp = rewriter.create(loc, lb, length_index, step); + auto idx = forOp.getInductionVar(); + rewriter.setInsertionPointToStart(forOp.getBody()); + + auto loadedInput = rewriter.create( + loc, meanOpAdaptor.getInput(), ValueRange{idx}); + auto loadedOutput = + rewriter.create(loc, alloc_output, ValueRange{}); + auto added_output = + rewriter.create(loc, loadedInput, loadedOutput); + rewriter.create(loc, added_output, alloc_output, + ValueRange{}); + + rewriter.setInsertionPointAfter(forOp); + + auto loadedOutput2 = + rewriter.create(loc, alloc_output, ValueRange{}); + auto divided_output = + rewriter.create(loc, loadedOutput2, loadedLength); + rewriter.create(loc, divided_output, alloc_output, + ValueRange{}); + + rewriter.replaceOp(op, alloc_output); + + return success(); + } +}; + +struct DiffOpLowering : public ConversionPattern { + DiffOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::DiffOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + + // Get the location of GainOp + auto loc = op->getLoc(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + auto memRefType = convertTensorToMemRef(tensorType); + auto alloc_output = insertAllocAndDealloc(memRefType, loc, rewriter); + + typename dsp::DiffOp::Adaptor diffOpAdaptor(operands); + + Value cst_idx_zero = rewriter.create(loc, 0); + Value cst_idx_one = rewriter.create(loc, 1); + + auto lengthArgType = + llvm::dyn_cast(op->getOperand(1).getType()); + + int lengthArgShape = lengthArgType.getShape().size(); + + ValueRange lengthValueRange; + + if (lengthArgShape == 0) + lengthValueRange = ValueRange{}; + else + lengthValueRange = ValueRange{cst_idx_zero}; + + auto loadedLength = rewriter.create( + loc, diffOpAdaptor.getLength(), lengthValueRange); + + // f64 to index + Value length_ui = rewriter.create( + loc, rewriter.getIntegerType(32), loadedLength); + Value length_index = rewriter.create( + loc, rewriter.getIndexType(), length_ui); + Value length_index_minus = + rewriter.create(loc, length_index, cst_idx_one); + + // loop for 0 <= i < N-1 + // Note: we need to use scf.for and memref::LoadOp/StoreOp (can we use + // dynamic ub for affine.for?) + auto lb = rewriter.create(loc, 0); + auto step = rewriter.create(loc, 1); + auto forOp = rewriter.create(loc, lb, length_index_minus, step); + auto idx = forOp.getInductionVar(); + rewriter.setInsertionPointToStart(forOp.getBody()); + + Value constant_index_one = rewriter.create( + loc, rewriter.getIndexType(), rewriter.getIndexAttr(1)); + Value idx_next = + rewriter.create(loc, idx, constant_index_one); + + auto input_current = rewriter.create( + loc, diffOpAdaptor.getInput(), ValueRange{idx}); + auto input_next = rewriter.create( + loc, diffOpAdaptor.getInput(), ValueRange{idx_next}); + + auto diff_input = + rewriter.create(loc, input_next, input_current); + rewriter.create(loc, diff_input, alloc_output, + ValueRange{idx}); + + rewriter.setInsertionPointAfter(forOp); + + rewriter.replaceOp(op, alloc_output); + + return success(); + } +}; + +struct GetSingleElemAtIdxOpLowering : public ConversionPattern { + GetSingleElemAtIdxOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::GetSingleElemAtIdxOp::getOperationName(), 1, + ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + // auto tensorType = + // llvm::cast((*op->result_type_begin())); auto + // memRefType = convertTensorToMemRef(tensorType); + auto memRefType = MemRefType::get({}, rewriter.getF64Type()); + auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); + + typename dsp::GetSingleElemAtIdxOp::Adaptor getSingleElemAtIdxAdaptor( + operands); + + auto indxArgType = + llvm::dyn_cast(op->getOperand(1).getType()); + + int indxArgShape = indxArgType.getShape().size(); + + ValueRange indexValueRange; + + if (indxArgShape == 0) + indexValueRange = ValueRange{}; + else { + Value cst_idx_zero = rewriter.create(loc, 0); + indexValueRange = ValueRange{cst_idx_zero}; + } + + Value loadedIndx = rewriter.create( + loc, getSingleElemAtIdxAdaptor.getIndx(), indexValueRange); + + // f64 to index + Value indx_ui = rewriter.create( + loc, rewriter.getIntegerType(32), loadedIndx); + Value indx_index = rewriter.create( + loc, rewriter.getIndexType(), indx_ui); + + Value loadedElement = rewriter.create( + loc, getSingleElemAtIdxAdaptor.getInput(), ValueRange{indx_index}); + + rewriter.create(loc, loadedElement, alloc, ValueRange{}); + + rewriter.replaceOp(op, alloc); + + return success(); + } +}; + +struct Diff2MeanOptimizedOpLowering : public ConversionPattern { + Diff2MeanOptimizedOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::Diff2MeanOptimizedOp::getOperationName(), 1, + ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + + // Get the location of GainOp + auto loc = op->getLoc(); + + // output for result type + auto tensorType = llvm::cast((*op->result_type_begin())); + + auto memRefType = convertTensorToMemRef(tensorType); + auto alloc_output = insertAllocAndDealloc(memRefType, loc, rewriter); + + typename dsp::Diff2MeanOptimizedOp::Adaptor diff2MeanOptimizedOpAdaptor( + operands); + + Value cst_idx_zero = rewriter.create(loc, 0); + + auto lengthArgType = + llvm::dyn_cast(op->getOperand(1).getType()); + + int lengthArgShape = lengthArgType.getShape().size(); + + ValueRange lengthValueRange; + + if (lengthArgShape == 0) + lengthValueRange = ValueRange{}; + else + lengthValueRange = ValueRange{cst_idx_zero}; + + auto loadedLength = rewriter.create( + loc, diff2MeanOptimizedOpAdaptor.getLength(), lengthValueRange); + + // f64 to index + Value length_ui = rewriter.create( + loc, rewriter.getIntegerType(32), loadedLength); + Value length_index = rewriter.create( + loc, rewriter.getIndexType(), length_ui); + + auto input_first = rewriter.create( + loc, diff2MeanOptimizedOpAdaptor.getInput(), ValueRange{cst_idx_zero}); + auto input_last = rewriter.create( + loc, diff2MeanOptimizedOpAdaptor.getInput(), ValueRange{length_index}); + + auto diff_input = + rewriter.create(loc, input_last, input_first); + + auto div_input = + rewriter.create(loc, diff_input, loadedLength); + + rewriter.create(loc, div_input, alloc_output, + ValueRange{}); rewriter.replaceOp(op, alloc_output); @@ -8022,10 +8384,11 @@ struct QamModulateRealOpLowering : public ConversionPattern { Value out = rewriter.create(loc, zeroReal, negOneVal, oneVal); - rewriter.create(loc, out, alloc, outputRealMap, ValueRange{ivI}); + rewriter.create(loc, out, alloc, outputRealMap, + ValueRange{ivI}); - rewriter.setInsertionPointAfter(forOpI); - rewriter.replaceOp(op, alloc); + rewriter.setInsertionPointAfter(forOpI); + rewriter.replaceOp(op, alloc); return success(); } @@ -8121,7 +8484,7 @@ struct QamDemodulateOpLowering : public ConversionPattern { llvm::dyn_cast(op->getOperand(0).getType()); llvm::ArrayRef realShape = realType.getShape(); - llvm::ArrayRef outputShape = output.getShape(); + // llvm::ArrayRef outputShape = output.getShape(); // constant vals; Value negOneVal = rewriter.create( @@ -8175,8 +8538,8 @@ struct QamDemodulateOpLowering : public ConversionPattern { rewriter.create(loc, out2, alloc, outputMapImagine, ValueRange{ivI}); - rewriter.setInsertionPointAfter(forOpI); - rewriter.replaceOp(op, alloc); + rewriter.setInsertionPointAfter(forOpI); + rewriter.replaceOp(op, alloc); return success(); } @@ -8197,10 +8560,10 @@ struct BeamFormOpLowering : public ConversionPattern { auto loc = op->getLoc(); auto beamFormOp = llvm::dyn_cast(op); - // allocating space for output - auto output = llvm::dyn_cast((*op->result_type_begin())); - auto outputMemRefType = convertTensorToMemRef(output); - auto alloc = insertAllocAndDealloc(outputMemRefType, loc, rewriter); + // allocating space for output + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMemRefType = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMemRefType, loc, rewriter); BeamFormOpAdaptor beamFormAdaptor(operands); auto time = beamFormAdaptor.getTime(); @@ -8211,14 +8574,14 @@ struct BeamFormOpLowering : public ConversionPattern { int64_t antennas = beamFormOp.getAntennas(); int64_t frequency = beamFormOp.getFreq(); - llvm::ArrayRef signalShape{antennas, timeDim}; - auto signalType = output.clone(signalShape); - - auto signalMemRefType = convertTensorToMemRef(signalType); - auto allocSignal = insertAllocAndDealloc(signalMemRefType, loc, rewriter); - - AffineExpr d0, d1; // i, j for generated signal dimension - bindDims(rewriter.getContext(), d0, d1); + llvm::ArrayRef signalShape{antennas, timeDim}; + auto signalType = output.clone(signalShape); + + auto signalMemRefType = convertTensorToMemRef(signalType); + auto allocSignal = insertAllocAndDealloc(signalMemRefType, loc, rewriter); + + AffineExpr d0, d1; // i, j for generated signal dimension + bindDims(rewriter.getContext(), d0, d1); // generated input map AffineMap genInputMap = @@ -8233,18 +8596,23 @@ struct BeamFormOpLowering : public ConversionPattern { AffineMap outputMap = AffineMap::get(2, 0, ArrayRef{d0}, rewriter.getContext()); - auto pi = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.1415926)); - auto two = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(2)); - auto four = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(4)); - auto two_pi = rewriter.create(loc, pi, two); // 2 * pi - auto freq_val = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(frequency)); - auto phase_var = rewriter.create(loc, two_pi, freq_val); - - // for loop from 0 to phase - int64_t lb = 0, ub = antennas, step=1; - affine::AffineForOp forOpI = rewriter.create(loc, lb, ub, step); - auto ivI = forOpI.getInductionVar(); // i : phase - rewriter.setInsertionPointToStart(forOpI.getBody()); + auto pi = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.1415926)); + auto two = rewriter.create(loc, rewriter.getF64Type(), + rewriter.getF64FloatAttr(2)); + auto four = rewriter.create(loc, rewriter.getF64Type(), + rewriter.getF64FloatAttr(4)); + auto two_pi = rewriter.create(loc, pi, two); // 2 * pi + auto freq_val = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(frequency)); + auto phase_var = rewriter.create(loc, two_pi, freq_val); + + // for loop from 0 to phase + int64_t lb = 0, ub = antennas, step = 1; + affine::AffineForOp forOpI = + rewriter.create(loc, lb, ub, step); + auto ivI = forOpI.getInductionVar(); // i : phase + rewriter.setInsertionPointToStart(forOpI.getBody()); // get the induction var to phase variable auto intType = rewriter.getI64Type(); @@ -8256,205 +8624,247 @@ struct BeamFormOpLowering : public ConversionPattern { auto iter_args = rewriter.create(loc, iter_tmp, four); // i*pi/4 - // for loop from 0 to timeDim - ub = timeDim; - affine::AffineForOp forOpJ = rewriter.create(loc, lb, ub, step); - auto ivJ = forOpJ.getInductionVar(); // i : phase - rewriter.setInsertionPointToStart(forOpJ.getBody()); - - // loop body - auto time_var = rewriter.create(loc, time, timeMap, ValueRange{ivI, ivJ}); - auto mul_var = rewriter.create(loc, time_var, phase_var); - auto sin_body = rewriter.create(loc, mul_var, iter_args); - auto result = rewriter.create(loc, sin_body); - rewriter.create(loc, result, allocSignal, ValueRange{ivI, ivJ}); - - rewriter.setInsertionPointAfter(forOpJ); - rewriter.setInsertionPointAfter(forOpI); - - ub = antennas; - affine::AffineForOp forOpIOut = rewriter.create(loc, lb, ub, step); - auto ivIoutput = forOpIOut.getInductionVar(); - rewriter.setInsertionPointToStart(forOpIOut.getBody()); - - ub = timeDim; - affine::AffineForOp forOpJOut = rewriter.create(loc, lb, ub, step); - auto ivJoutput = forOpJOut.getInductionVar(); - rewriter.setInsertionPointToStart(forOpJOut.getBody()); - - // load from signal input - auto signalInput = rewriter.create(loc, allocSignal, genInputMap, ValueRange{ivIoutput, ivJoutput}); - auto weight = rewriter.create(loc, weights, outputMap, ValueRange{ivIoutput, ivJoutput}); - auto intermediateVal = rewriter.create(loc, signalInput, weight); - - // load from output - auto outputVal = rewriter.create(loc, alloc, ValueRange{ivJoutput}); - auto beamOut = rewriter.create(loc, intermediateVal, outputVal); - - rewriter.create(loc, beamOut, alloc, ValueRange{ivJoutput}); + // for loop from 0 to timeDim + ub = timeDim; + affine::AffineForOp forOpJ = + rewriter.create(loc, lb, ub, step); + auto ivJ = forOpJ.getInductionVar(); // i : phase + rewriter.setInsertionPointToStart(forOpJ.getBody()); + + // loop body + auto time_var = + rewriter.create(loc, time, timeMap, ValueRange{ivI, ivJ}); + auto mul_var = rewriter.create(loc, time_var, phase_var); + auto sin_body = rewriter.create(loc, mul_var, iter_args); + auto result = rewriter.create(loc, sin_body); + rewriter.create(loc, result, allocSignal, + ValueRange{ivI, ivJ}); + + rewriter.setInsertionPointAfter(forOpJ); + rewriter.setInsertionPointAfter(forOpI); + + ub = antennas; + affine::AffineForOp forOpIOut = + rewriter.create(loc, lb, ub, step); + auto ivIoutput = forOpIOut.getInductionVar(); + rewriter.setInsertionPointToStart(forOpIOut.getBody()); + + ub = timeDim; + affine::AffineForOp forOpJOut = + rewriter.create(loc, lb, ub, step); + auto ivJoutput = forOpJOut.getInductionVar(); + rewriter.setInsertionPointToStart(forOpJOut.getBody()); + + // load from signal input + auto signalInput = rewriter.create( + loc, allocSignal, genInputMap, ValueRange{ivIoutput, ivJoutput}); + auto weight = rewriter.create( + loc, weights, outputMap, ValueRange{ivIoutput, ivJoutput}); + auto intermediateVal = + rewriter.create(loc, signalInput, weight); + + // load from output + auto outputVal = + rewriter.create(loc, alloc, ValueRange{ivJoutput}); + auto beamOut = + rewriter.create(loc, intermediateVal, outputVal); + + rewriter.create(loc, beamOut, alloc, ValueRange{ivJoutput}); rewriter.setInsertionPointAfter(forOpJOut); rewriter.setInsertionPointAfter(forOpIOut); - rewriter.replaceOp(op, alloc); - - return mlir::success(); + rewriter.replaceOp(op, alloc); - } + return mlir::success(); + } }; struct SpaceModulateOpLowering : public ConversionPattern { - SpaceModulateOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::SpaceModulateOp::getOperationName(), 1, ctx) {} - - mlir::LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - auto loc = op->getLoc(); - - // output - auto output = llvm::dyn_cast((*op->result_type_begin())); - auto outputMem = convertTensorToMemRef(output); - auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); - - SpaceModulateOpAdaptor spaceModAdaptor(operands); - Value signal = spaceModAdaptor.getSignal(); - auto signalType = llvm::dyn_cast(op->getOperand(0).getType()); - llvm::ArrayRef signalShape = signalType.getShape(); - - Value negOneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); - Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - Value oneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); - - // one dim loop - int64_t lb=0, ub=signalShape[0], step=1; - AffineForOp forOp = rewriter.create(loc, lb, ub, step); - rewriter.setInsertionPointToStart(forOp.getBody()); - auto iv = forOp.getInductionVar(); - - Value bit = rewriter.create(loc, signal, ValueRange{iv}); - - Value isOne = rewriter.create(loc, arith::CmpFPredicate::OEQ, bit, oneVal); - - auto out = rewriter.create(loc, isOne, oneVal, negOneVal); - - rewriter.create(loc, out, alloc, ValueRange{iv}); - rewriter.setInsertionPointAfter(forOp); - - rewriter.replaceOp(op, alloc); - return mlir::success(); - } + SpaceModulateOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::SpaceModulateOp::getOperationName(), 1, ctx) {} + + mlir::LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + // output + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); + + SpaceModulateOpAdaptor spaceModAdaptor(operands); + Value signal = spaceModAdaptor.getSignal(); + auto signalType = + llvm::dyn_cast(op->getOperand(0).getType()); + llvm::ArrayRef signalShape = signalType.getShape(); + + Value negOneVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + // Value zeroVal = rewriter.create( + // loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value oneVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + + // one dim loop + int64_t lb = 0, ub = signalShape[0], step = 1; + AffineForOp forOp = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOp.getBody()); + auto iv = forOp.getInductionVar(); + + Value bit = rewriter.create(loc, signal, ValueRange{iv}); + + Value isOne = rewriter.create(loc, arith::CmpFPredicate::OEQ, + bit, oneVal); + + auto out = rewriter.create(loc, isOne, oneVal, negOneVal); + + rewriter.create(loc, out, alloc, ValueRange{iv}); + rewriter.setInsertionPointAfter(forOp); + + rewriter.replaceOp(op, alloc); + return mlir::success(); + } }; // space modulate struct SpaceDemodulateOpLowering : public ConversionPattern { - SpaceDemodulateOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::SpaceDemodulateOp::getOperationName(), 1, ctx) {} + SpaceDemodulateOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::SpaceDemodulateOp::getOperationName(), 1, ctx) {} - mlir::LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - auto loc = op->getLoc(); + mlir::LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); - // output - auto output = llvm::dyn_cast((*op->result_type_begin())); - auto outputMem = convertTensorToMemRef(output); - auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); + // output + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); - SpaceDemodulateOpAdaptor spaceDemodAdaptor(operands); - Value binary = spaceDemodAdaptor.getBinary(); - auto binaryType = llvm::dyn_cast(op->getOperand(0).getType()); - llvm::ArrayRef binaryShape = binaryType.getShape(); + SpaceDemodulateOpAdaptor spaceDemodAdaptor(operands); + Value binary = spaceDemodAdaptor.getBinary(); + auto binaryType = + llvm::dyn_cast(op->getOperand(0).getType()); + llvm::ArrayRef binaryShape = binaryType.getShape(); - Value negOneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); - Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - Value oneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + // Value negOneVal = rewriter.create( + // loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + Value zeroVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value oneVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); - // one dim loop - int64_t lb=0, ub=binaryShape[0], step=1; - AffineForOp forOp = rewriter.create(loc, lb, ub, step); - rewriter.setInsertionPointToStart(forOp.getBody()); - auto iv = forOp.getInductionVar(); + // one dim loop + int64_t lb = 0, ub = binaryShape[0], step = 1; + AffineForOp forOp = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOp.getBody()); + auto iv = forOp.getInductionVar(); - Value bit = rewriter.create(loc, binary, ValueRange{iv}); + Value bit = rewriter.create(loc, binary, ValueRange{iv}); - Value isOne = rewriter.create(loc, arith::CmpFPredicate::OGE, bit, oneVal); + Value isOne = rewriter.create(loc, arith::CmpFPredicate::OGE, + bit, oneVal); - auto out = rewriter.create(loc, isOne, oneVal, zeroVal); + auto out = rewriter.create(loc, isOne, oneVal, zeroVal); - rewriter.create(loc, out, alloc, ValueRange{iv}); + rewriter.create(loc, out, alloc, ValueRange{iv}); - rewriter.setInsertionPointAfter(forOp); - rewriter.replaceOp(op, alloc); - return mlir::success(); - } + rewriter.setInsertionPointAfter(forOp); + rewriter.replaceOp(op, alloc); + return mlir::success(); + } }; // soace demodulate struct SpaceErrCorrectionOpLowering : public ConversionPattern { - SpaceErrCorrectionOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::SpaceErrCorrectionOp::getOperationName(), 1, ctx) {} + SpaceErrCorrectionOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::SpaceErrCorrectionOp::getOperationName(), 1, + ctx) {} - mlir::LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - auto loc = op->getLoc(); + mlir::LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); - // output - auto output = llvm::dyn_cast((*op->result_type_begin())); - auto outputMem = convertTensorToMemRef(output); - auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); + // output + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMem = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMem, loc, rewriter); - SpaceErrCorrectionOpAdaptor adaptor(operands); - Value signal = adaptor.getSignal(); - auto signalType = llvm::dyn_cast(op->getOperand(0).getType()); - llvm::ArrayRef signalShape = signalType.getShape(); + SpaceErrCorrectionOpAdaptor adaptor(operands); + Value signal = adaptor.getSignal(); + auto signalType = + llvm::dyn_cast(op->getOperand(0).getType()); + llvm::ArrayRef signalShape = signalType.getShape(); - Value zeroVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); - Value oneVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); - Value twoVal = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(2)); + Value zeroVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value oneVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + Value twoVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(2)); - AffineExpr d0, d1; - bindDims(rewriter.getContext(), d0, d1); - AffineMap first = AffineMap::get(2, 0, ArrayRef{d0}, rewriter.getContext()); - AffineMap index = AffineMap::get(2, 0, ArrayRef{d0+d1}, rewriter.getContext()); + AffineExpr d0, d1; + bindDims(rewriter.getContext(), d0, d1); + AffineMap first = + AffineMap::get(2, 0, ArrayRef{d0}, rewriter.getContext()); + AffineMap index = AffineMap::get(2, 0, ArrayRef{d0 + d1}, + rewriter.getContext()); - int64_t lb=0, ub=signalShape[0], step=8; - AffineForOp forOpI = rewriter.create(loc, lb, ub, step); - rewriter.setInsertionPointToStart(forOpI.getBody()); - auto ivI = forOpI.getInductionVar(); + int64_t lb = 0, ub = signalShape[0], step = 8; + AffineForOp forOpI = rewriter.create(loc, lb, ub, step); + rewriter.setInsertionPointToStart(forOpI.getBody()); + auto ivI = forOpI.getInductionVar(); - auto firstVal = rewriter.create(loc, signal, ValueRange{ivI}); // signal [0] - rewriter.create(loc, firstVal, alloc, ValueRange{ivI}); // store signal[0] to alloc[0] + auto firstVal = rewriter.create( + loc, signal, ValueRange{ivI}); // signal [0] + rewriter.create( + loc, firstVal, alloc, ValueRange{ivI}); // store signal[0] to alloc[0] - int64_t inner_lb=1, inner_ub=8, inner_step=1; - AffineForOp forOpJ = rewriter.create(loc, inner_lb, inner_ub, inner_step); - rewriter.setInsertionPointToStart(forOpJ.getBody()); - auto ivJ = forOpJ.getInductionVar(); + int64_t inner_lb = 1, inner_ub = 8, inner_step = 1; + AffineForOp forOpJ = + rewriter.create(loc, inner_lb, inner_ub, inner_step); + rewriter.setInsertionPointToStart(forOpJ.getBody()); + auto ivJ = forOpJ.getInductionVar(); - auto stored = rewriter.create(loc, alloc, first, ValueRange{ivI, ivJ}); // load alloc[0] - auto loaded = rewriter.create(loc, signal, index, ValueRange{ivI, ivJ}); // load signal[1...7] + auto stored = rewriter.create( + loc, alloc, first, ValueRange{ivI, ivJ}); // load alloc[0] + auto loaded = rewriter.create( + loc, signal, index, ValueRange{ivI, ivJ}); // load signal[1...7] - auto added = rewriter.create(loc, stored, loaded); // add - rewriter.create(loc, added, alloc, ValueRange{ivI}); // store val to alloc[0] - rewriter.create(loc, loaded, alloc, index, ValueRange{ivI, ivJ}); // store val to alloc[1...7] + auto added = rewriter.create(loc, stored, loaded); // add + rewriter.create(loc, added, alloc, + ValueRange{ivI}); // store val to alloc[0] + rewriter.create( + loc, loaded, alloc, index, + ValueRange{ivI, ivJ}); // store val to alloc[1...7] - rewriter.setInsertionPointAfter(forOpJ); + rewriter.setInsertionPointAfter(forOpJ); - auto initVal = rewriter.create(loc, signal, ValueRange{ivI}); // load signal[0] - auto oneCount = rewriter.create(loc, alloc, ValueRange{ivI}); // load alloc[0] - auto parityCheck = rewriter.create(loc, oneCount, twoVal); // get remainder from oneCount / 2 -> either 1 or 0 + auto initVal = rewriter.create( + loc, signal, ValueRange{ivI}); // load signal[0] + auto oneCount = rewriter.create( + loc, alloc, ValueRange{ivI}); // load alloc[0] + auto parityCheck = rewriter.create( + loc, oneCount, + twoVal); // get remainder from oneCount / 2 -> either 1 or 0 - auto oddParity = rewriter.create(loc, arith::CmpFPredicate::OEQ, oneVal, parityCheck); // if paritycheck == 1 - auto valToAlloc = rewriter.create(loc, oddParity, zeroVal, initVal); // if true: valToAlloc = 0 else NC + auto oddParity = + rewriter.create(loc, arith::CmpFPredicate::OEQ, oneVal, + parityCheck); // if paritycheck == 1 + auto valToAlloc = rewriter.create( + loc, oddParity, zeroVal, initVal); // if true: valToAlloc = 0 else NC - rewriter.create(loc, valToAlloc, alloc, ValueRange{ivI}); // store the value to alloc[0] + rewriter.create( + loc, valToAlloc, alloc, ValueRange{ivI}); // store the value to alloc[0] - rewriter.setInsertionPointAfter(forOpI); + rewriter.setInsertionPointAfter(forOpI); - rewriter.replaceOp(op, alloc); - return mlir::success(); - } + rewriter.replaceOp(op, alloc); + return mlir::success(); + } }; -}// namespace +} // namespace //===----------------------------------------------------------------------===// // ToyToAffineLoweringPass @@ -8488,7 +8898,7 @@ void ToyToAffineLoweringPass::runOnOperation() { target.addLegalDialect(); + scf::SCFDialect>(); // We also define the Toy dialect as Illegal so that the conversion will fail // if any of these operations are *not* converted. Given that we actually want @@ -8526,9 +8936,12 @@ void ToyToAffineLoweringPass::runOnOperation() { FIRFilterYSymmOptimizedOpLowering, FFT1DRealSymmOpLowering, FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering, ShiftRightOpLowering, MatmulOpLowering, - ThresholdUpOpLowering, QamModulateRealOpLowering, QamModulateImgOpLowering, - QamDemodulateOpLowering, FindPeaksOpLowering, BeamFormOpLowering, - SpaceModulateOpLowering, SpaceDemodulateOpLowering, SpaceErrCorrectionOpLowering>(&getContext()); + ThresholdUpOpLowering, QamModulateRealOpLowering, + QamModulateImgOpLowering, QamDemodulateOpLowering, FindPeaksOpLowering, + BeamFormOpLowering, SpaceModulateOpLowering, SpaceDemodulateOpLowering, + SpaceErrCorrectionOpLowering, FindPeaksOpLowering, MaxOpLowering, + MeanOpLowering, DiffOpLowering, GetSingleElemAtIdxOpLowering, Diff2MeanOptimizedOpLowering>( + &getContext()); // With the target and rewrite patterns defined, we can now attempt the // conversion. The conversion will signal failure if any of our `illegal` diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index 6ad8054172b1..dfa56759e902 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -33,13 +33,13 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" #include "llvm/Support/raw_ostream.h" +#include #include #include #include #include #include #include -#include using namespace mlir::dsp; using namespace dsp; @@ -409,15 +409,47 @@ class MLIRGenImpl { } return builder.create(location, operands[0]); } - + // FindPeaks Op - if(callee == "find_peaks"){ - if(call.getArgs().size() != 3){ - emitError(location, "MLIR codegen encountered an error: dsp.find_peaks " - "accepts only 3 arguments: signal, height, and distance"); - return nullptr; - } - return builder.create(location, operands[0], operands[1], operands[2]); + if (callee == "find_peaks") { + if (call.getArgs().size() != 3) { + emitError(location, + "MLIR codegen encountered an error: dsp.find_peaks " + "accepts only 3 arguments: signal, height, and distance"); + return nullptr; + } + return builder.create(location, operands[0], operands[1], + operands[2]); + } + + // Max Op + if (callee == "max") { + if (call.getArgs().size() != 1) { + emitError(location, "MLIR codegen encountered an error: dsp.max " + "accepts only 1 argument."); + return nullptr; + } + return builder.create(location, operands[0]); + } + + // Mean Op + if (callee == "mean") { + if (call.getArgs().size() != 2) { + emitError(location, "MLIR codegen encountered an error: dsp.mean " + "accepts only 2 arguments: input tensor, length"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); + } + + // Diff Op + if (callee == "diff") { + if (call.getArgs().size() != 2) { + emitError(location, "MLIR codegen encountered an error: dsp.diff " + "accepts only 2 arguments: input tensor, legnth"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); } // Shift right Op @@ -461,13 +493,14 @@ class MLIRGenImpl { operands[1]); } - if(callee == "medianFilter"){ - if(call.getArgs().size() != 1){ - emitError(location, "MLIR codegen encountered an error: dsp.medianFilter " - "accepts only 1 argument"); + if (callee == "medianFilter") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.medianFilter " + "accepts only 1 argument"); return nullptr; - } - return builder.create(location, operands[0] ); + } + return builder.create(location, operands[0]); } if (callee == "slidingWindowAvg") { @@ -651,6 +684,30 @@ class MLIRGenImpl { operands[1]); } + // Get Single Element At Op + if (callee == "getSingleElemAtIndx") { + if (call.getArgs().size() != 2) { + emitError(location, + "MLIR codegen encountered an error: dsp.getSingleElemAtIndx " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], + operands[1]); + } + + // Diff2MeanOptimized Op + if (callee == "diff2meanOpt") { + if (call.getArgs().size() != 2) { + emitError(location, + "MLIR codegen encountered an error: dsp.diff2meanOpt " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], + operands[1]); + } + // Set Elem At Indx if (callee == "setElemAtIndx") { if (call.getArgs().size() != 3) { @@ -893,65 +950,72 @@ class MLIRGenImpl { double antenna = antennaVal[0].getValueAsDouble(); double freq = freqVal[0].getValueAsDouble(); - return builder.create(location, antenna, freq, - operands[2], operands[3]); + return builder.create(location, antenna, freq, operands[2], + operands[3]); + } + // qam modulate op + if (callee == "qam_modulate_real") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.QamModulateRealOp " + "accepts 1 arguments"); + return nullptr; + } + + return builder.create(location, operands[0]); + } + + if (callee == "qam_modulate_imagine") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.QamModualteImgOp " + "accepts 1 arguments"); + return nullptr; + } + + return builder.create(location, operands[0]); + } + // qam_demodulate + if (callee == "qam_demodulate") { + if (call.getArgs().size() != 2) { + emitError(location, + "MLIR codegen encountered an error: dsp.QamDemodulateOp" + "accepts 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], + operands[1]); + } + // space_demodulate + if (callee == "space_demodulate") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.SpaceDemodulateOp" + "accepts 1 arguments"); + return nullptr; + } + return builder.create(location, operands[0]); + } + // space_modulate + if (callee == "space_modulate") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.SpaceModulateOp" + "accepts 1 arguments"); + return nullptr; + } + return builder.create(location, operands[0]); + } + // space_err_correction + if (callee == "space_err_correction") { + if (call.getArgs().size() != 1) { + emitError(location, + "MLIR codegen encountered an error: dsp.SpaceErrCorrectionOp" + "accepts 1 arguments"); + return nullptr; + } + return builder.create(location, operands[0]); } - // qam modulate op - if(callee == "qam_modulate_real") { - if(call.getArgs().size() != 1) { - emitError(location, "MLIR codegen encountered an error: dsp.QamModulateRealOp " - "accepts 1 arguments"); - return nullptr; - } - - return builder.create(location, operands[0]); - } - - if(callee == "qam_modulate_imagine"){ - if(call.getArgs().size() != 1) { - emitError(location, "MLIR codegen encountered an error: dsp.QamModualteImgOp " - "accepts 1 arguments"); - return nullptr; - } - - return builder.create(location, operands[0]); - } - // qam_demodulate - if(callee == "qam_demodulate") { - if(call.getArgs().size() != 2) { - emitError(location, "MLIR codegen encountered an error: dsp.QamDemodulateOp" - "accepts 2 arguments"); - return nullptr; - } - return builder.create(location, operands[0], operands[1]); - } - // space_demodulate - if(callee == "space_demodulate") { - if(call.getArgs().size() != 1) { - emitError(location, "MLIR codegen encountered an error: dsp.SpaceDemodulateOp" - "accepts 1 arguments"); - return nullptr; - } - return builder.create(location, operands[0]); - } - // space_modulate - if(callee == "space_modulate") { - if(call.getArgs().size() != 1) { - emitError(location, "MLIR codegen encountered an error: dsp.SpaceModulateOp" - "accepts 1 arguments"); - return nullptr; - } - return builder.create(location, operands[0]); - } - // space_err_correction - if(callee == "space_err_correction") { - if(call.getArgs().size() != 1) { - emitError(location, "MLIR codegen encountered an error: dsp.SpaceErrCorrectionOp" - "accepts 1 arguments"); - return nullptr; - } - return builder.create(location, operands[0]); - } // Builtin calls have their custom operation, meaning this is a // straightforward emission. // if(callee == "delay"){ @@ -984,23 +1048,26 @@ class MLIRGenImpl { mlir::Value mlirGen(NumberExprAST &num) { return builder.create(loc(num.loc()), num.getValue()); } - + /// Emit a string exression mlir::Value mlirGen(StringExprAST &expr) { auto string_val = expr.getStringVal(); - + std::vector signals; - for(char ch : string_val) { - std::bitset<8> bits(static_cast(ch)), reversed; - int n = 8; - for(int i=0; i bits(static_cast(ch)), reversed; + int n = 8; + for (int i = 0; i < n; ++i) + reversed[i] = bits[n - i - 1]; + for (int i = 0; i < n; ++i) + signals.push_back(reversed[i]); } mlir::Type eleType = builder.getF64Type(); auto dataType = mlir::RankedTensorType::get(signals.size(), eleType); - auto dataAttr = mlir::DenseElementsAttr::get(dataType, llvm::ArrayRef(signals)); + auto dataAttr = + mlir::DenseElementsAttr::get(dataType, llvm::ArrayRef(signals)); auto type = getType(signals.size()); @@ -1042,22 +1109,21 @@ class MLIRGenImpl { return nullptr; } - mlir::Value value; // Register the value in the symbol table. value = mlirGen(*init); if (!value) - return nullptr; + return nullptr; - // We have the initializer value, but in case the variable was declared - // with specific shape, we emit a "reshape" operation. It will get - // optimized out later as needed. + // We have the initializer value, but in case the variable was declared + // with specific shape, we emit a "reshape" operation. It will get + // optimized out later as needed. if (!vardecl.getType().shape.empty()) { - value = builder.create(loc(vardecl.loc()), - getType(vardecl.getType()), value); + value = builder.create(loc(vardecl.loc()), + getType(vardecl.getType()), value); } if (failed(declare(vardecl.getName(), value))) - return nullptr; + return nullptr; return value; } diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp index e2c461afa434..ff58c476388d 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp @@ -11,15 +11,20 @@ // //===----------------------------------------------------------------------===// +#include "mlir/Dialect/Affine/IR/AffineOps.h" +#include "mlir/Dialect/Arith/IR/Arith.h" +#include "mlir/Dialect/MemRef/IR/MemRef.h" + #include "mlir/IR/MLIRContext.h" #include "mlir/IR/PatternMatch.h" #include "mlir/IR/Value.h" #include "mlir/Support/LogicalResult.h" -#include "toy/Dialect.h" #include "toy/DebugConfig.h" +#include "toy/Dialect.h" #include using namespace mlir; using namespace dsp; +using namespace std; namespace { /// Include the patterns defined in the Declarative Rewrite framework. @@ -58,16 +63,15 @@ struct SimplifyRedundantTranspose : public mlir::OpRewritePattern { } }; - - -//Pseudo-Code -//Find back to back gain operation - // result1 = upsampling(input1, rate1) - // result2 = downsampling(result1, rate2) +// Pseudo-Code +// Find back to back gain operation +// result1 = upsampling(input1, rate1) +// result2 = downsampling(result1, rate2) // if rate1 == rate2 then result2 = input1 - // result2 will be now delay(input1, gain1 + gain2) - // replaceOp -struct SimplifyUpsamplingDownsampling : public mlir::OpRewritePattern { +// result2 will be now delay(input1, gain1 + gain2) +// replaceOp +struct SimplifyUpsamplingDownsampling + : public mlir::OpRewritePattern { /// We register this pattern to match every dsp.downsampling in the IR. /// The "benefit" is used by the framework to order the patterns and process /// them in order of profitability. @@ -83,152 +87,192 @@ struct SimplifyUpsamplingDownsampling : public mlir::OpRewritePattern(); + dsp::UpsamplingOp prev_UpSamplingOp = + downsamplingOperand0_input.getDefiningOp(); // Input defined by another downsampling? If not, no match. if (!prev_UpSamplingOp) return failure(); - //Get operands for UpSamplingOp + // Get operands for UpSamplingOp mlir::Value UpsamplingOperand1_Rate = prev_UpSamplingOp.getOperand(1); mlir::Value UpsamplingOperand0_input = prev_UpSamplingOp.getOperand(0); - //get constant value from the downsamplingOp -- operand1 - dsp::ConstantOp constant_Op1_downsamplingOp = downsamplingOperand1_Rate.getDefiningOp(); - // DEBUG_PRINT_NO_ARGS(); - DenseElementsAttr DenseValueFrmDownsampling = constant_Op1_downsamplingOp.getValue(); - // DEBUG_PRINT_NO_ARGS(); + // get constant value from the downsamplingOp -- operand1 + dsp::ConstantOp constant_Op1_downsamplingOp = + downsamplingOperand1_Rate.getDefiningOp(); + // DEBUG_PRINT_NO_ARGS(); + DenseElementsAttr DenseValueFrmDownsampling = + constant_Op1_downsamplingOp.getValue(); + // DEBUG_PRINT_NO_ARGS(); auto elements = DenseValueFrmDownsampling.getValues(); float FirstValue = elements[0].getValueAsDouble(); - int64_t DownsamplingRate = (int64_t) FirstValue; + int64_t DownsamplingRate = (int64_t)FirstValue; - //Get constant value from upsampling: -- operand1 - dsp::ConstantOp constant_Op1_upSamplingOp = UpsamplingOperand1_Rate.getDefiningOp(); - // DEBUG_PRINT_NO_ARGS(); - DenseElementsAttr DenseValueFrmUpsampling = constant_Op1_upSamplingOp.getValue(); - // DEBUG_PRINT_NO_ARGS(); + // Get constant value from upsampling: -- operand1 + dsp::ConstantOp constant_Op1_upSamplingOp = + UpsamplingOperand1_Rate.getDefiningOp(); + // DEBUG_PRINT_NO_ARGS(); + DenseElementsAttr DenseValueFrmUpsampling = + constant_Op1_upSamplingOp.getValue(); + // DEBUG_PRINT_NO_ARGS(); elements = DenseValueFrmUpsampling.getValues(); FirstValue = elements[0].getValueAsDouble(); - int64_t UpsamplingRate = (int64_t) FirstValue; - - llvm::errs() << "DownsamplingRate = " << DownsamplingRate << " UpsamplingRate" << UpsamplingRate << "\n"; - if(DownsamplingRate == UpsamplingRate) - { - // Otherwise, we have a redundant downsampling. Use the rewriter. - // rewriter.replaceOp(op, {downsamplingInputOp.getOperand()}); //downsamplingOperand0_input + int64_t UpsamplingRate = (int64_t)FirstValue; + + llvm::errs() << "DownsamplingRate = " << DownsamplingRate + << " UpsamplingRate" << UpsamplingRate << "\n"; + if (DownsamplingRate == UpsamplingRate) { + // Otherwise, we have a redundant downsampling. Use the rewriter. + // rewriter.replaceOp(op, {downsamplingInputOp.getOperand()}); + // //downsamplingOperand0_input llvm::errs() << "Going for Downsampling pass\n"; rewriter.replaceOp(op, UpsamplingOperand0_input); - return success(); + return success(); - } - else if(UpsamplingRate > DownsamplingRate) - { - //check if UpSamplingRate is a multiple of DownsamplingRate - //if yes, final result should be UpSampling with SamplingRate as division - if(UpsamplingRate % DownsamplingRate != 0) - { + } else if (UpsamplingRate > DownsamplingRate) { + // check if UpSamplingRate is a multiple of DownsamplingRate + // if yes, final result should be UpSampling with SamplingRate as division + if (UpsamplingRate % DownsamplingRate != 0) { return failure(); } // - if(DownsamplingRate == 0) - { - llvm::errs() << "DownSamplingRate= 0 Not allowed" << "\n"; + if (DownsamplingRate == 0) { + llvm::errs() << "DownSamplingRate= 0 Not allowed" << "\n"; return failure(); } - double finalUpSamplingRate = (double) UpsamplingRate / DownsamplingRate; + double finalUpSamplingRate = (double)UpsamplingRate / DownsamplingRate; - auto constOp_finalSamplingRate = rewriter.create(op.getLoc(), finalUpSamplingRate); + auto constOp_finalSamplingRate = + rewriter.create(op.getLoc(), finalUpSamplingRate); - auto finalUpSamplingOp = rewriter.create(op.getLoc(), - UpsamplingOperand0_input , constOp_finalSamplingRate); + auto finalUpSamplingOp = rewriter.create( + op.getLoc(), UpsamplingOperand0_input, constOp_finalSamplingRate); llvm::errs() << "Going for Downsampling pass\n"; rewriter.replaceOp(op, finalUpSamplingOp); - } return failure(); - } }; -//Pseudo-Code -//Find back to back gain operation - // result1 = gain(input1, gain1) - // result2 = gain(result1, gain2) +// Pseudo-Code +// Find back to back gain operation +// result1 = gain(input1, gain1) +// result2 = gain(result1, gain2) // if result1 is coming from another delay operation - // result2 will be now delay(input1, gain1 + gain2) - // replaceOp -struct SimplifyBack2BackGain: public mlir::OpRewritePattern{ +// result2 will be now delay(input1, gain1 + gain2) +// replaceOp +struct SimplifyBack2BackGain : public mlir::OpRewritePattern { // - SimplifyBack2BackGain(mlir::MLIRContext *context) - : OpRewritePattern(context, 1) {} - - mlir::LogicalResult matchAndRewrite(GainOp op, - mlir::PatternRewriter &rewriter) const override { - - // - mlir::Value gainOp_operand0 = op.getOperand(0); - - //check if this is coming from another gain operation - GainOp prev_gainOp = gainOp_operand0.getDefiningOp(); - - if(!prev_gainOp) - return failure(); + SimplifyBack2BackGain(mlir::MLIRContext *context) + : OpRewritePattern(context, 1) {} - mlir::Value gainOp_operand1 = op.getOperand(1); - mlir::Value prev_gainOp_operand0 = prev_gainOp.getOperand(0); - mlir::Value prev_gainOp_operand1 = prev_gainOp.getOperand(1); + mlir::LogicalResult + matchAndRewrite(GainOp op, mlir::PatternRewriter &rewriter) const override { + + // + mlir::Value gainOp_operand0 = op.getOperand(0); + + // check if this is coming from another gain operation + GainOp prev_gainOp = gainOp_operand0.getDefiningOp(); + + if (!prev_gainOp) + return failure(); + + mlir::Value gainOp_operand1 = op.getOperand(1); + mlir::Value prev_gainOp_operand0 = prev_gainOp.getOperand(0); + mlir::Value prev_gainOp_operand1 = prev_gainOp.getOperand(1); - //create add op - auto addOp = rewriter.create(op.getLoc(), prev_gainOp_operand1, gainOp_operand1); - auto newGainOp = rewriter.create(op.getLoc(), - prev_gainOp_operand0 , addOp.getResult()); - - //Repalce the use of original gain operation with this newGainOp + // create add op + auto addOp = rewriter.create(op.getLoc(), prev_gainOp_operand1, + gainOp_operand1); + auto newGainOp = rewriter.create(op.getLoc(), prev_gainOp_operand0, + addOp.getResult()); + + // Repalce the use of original gain operation with this newGainOp rewriter.replaceOp(op, newGainOp.getResult()); return mlir::success(); - - } + } }; +// Pseudo-Code +// Mean of diff is equal to (input[-1] - input[0])/len(input). +// For example, for array (a, b, c, d, e) +// diff(array) = (b-a, c-b, d-c, e-d) +// mean(diff(array)) = ((b-a) + (c-b) + (d-c) + (e-d))/4 = (e-a)/4 +// result1 = diff(input1, diff_length) //NOTE: len(result1) == diff_length-1 +// virtually (tensor size is fixed as len(input)-1). result2 = mean(result1, +// mean_length) +// if mean_length <= (diff_length-1), +// result2 will be now (input1[mean_length] - input[0])/mean_length +// replaceOp +struct SimplifyDiff2Mean : public mlir::OpRewritePattern { + // + SimplifyDiff2Mean(mlir::MLIRContext *context) + : OpRewritePattern(context, 1) {} -struct SimplifyBack2BackDelay: public mlir::OpRewritePattern{ + mlir::LogicalResult + matchAndRewrite(MeanOp op, mlir::PatternRewriter &rewriter) const override { + + // + mlir::Value meanOp_operand0 = op.getOperand(0); + + // check if this is coming from diff operation. + DiffOp prev_diffOp = meanOp_operand0.getDefiningOp(); + + if (!prev_diffOp) + return failure(); + + mlir::Value meanOp_operand1 = op.getOperand(1); + mlir::Value prev_diffOp_operand0 = prev_diffOp.getOperand(0); + + auto optimizedOp = rewriter.create( + op.getLoc(), prev_diffOp_operand0, meanOp_operand1); + + // Repalce the use of original diff operation with this operation + rewriter.replaceOp(op, optimizedOp.getResult()); + return mlir::success(); + } +}; + +struct SimplifyBack2BackDelay : public mlir::OpRewritePattern { // - SimplifyBack2BackDelay(mlir::MLIRContext *context) - : OpRewritePattern(context, 1) {} - - mlir::LogicalResult matchAndRewrite(DelayOp op, - mlir::PatternRewriter &rewriter) const override { - - // - mlir::Value delayOp_operand0 = op.getOperand(0); - - //check if this is coming from another delay operation - DelayOp prev_delayOp = delayOp_operand0.getDefiningOp(); - - if(!prev_delayOp) - return failure(); + SimplifyBack2BackDelay(mlir::MLIRContext *context) + : OpRewritePattern(context, 1) {} + + mlir::LogicalResult + matchAndRewrite(DelayOp op, mlir::PatternRewriter &rewriter) const override { + + // + mlir::Value delayOp_operand0 = op.getOperand(0); - mlir::Value delayOp_operand1 = op.getOperand(1); - mlir::Value prev_delayOp_operand0 = prev_delayOp.getOperand(0); - mlir::Value prev_delayOp_operand1 = prev_delayOp.getOperand(1); + // check if this is coming from another delay operation + DelayOp prev_delayOp = delayOp_operand0.getDefiningOp(); + + if (!prev_delayOp) + return failure(); - //create add op - auto addOp = rewriter.create(op.getLoc(), prev_delayOp_operand1, delayOp_operand1); - auto newDelayOp = rewriter.create(op.getLoc(), - prev_delayOp_operand0 , addOp.getResult()); - - //Repalce the use of original delay operation with this newDelayOp + mlir::Value delayOp_operand1 = op.getOperand(1); + mlir::Value prev_delayOp_operand0 = prev_delayOp.getOperand(0); + mlir::Value prev_delayOp_operand1 = prev_delayOp.getOperand(1); + + // create add op + auto addOp = rewriter.create(op.getLoc(), prev_delayOp_operand1, + delayOp_operand1); + auto newDelayOp = rewriter.create( + op.getLoc(), prev_delayOp_operand0, addOp.getResult()); + + // Repalce the use of original delay operation with this newDelayOp rewriter.replaceOp(op, newDelayOp.getResult()); return mlir::success(); - - } + } }; // Pseudo-code -// if operand of square is coming from real part of fft1d -// replace fft1d with fft1dreal +// if operand of square is coming from real part of fft1d +// replace fft1d with fft1dreal // still squareOp will remain same struct SimplifyFFTSquare : public mlir::OpRewritePattern { /// We register this pattern to match every dsp.downsampling in the IR. @@ -241,8 +285,7 @@ struct SimplifyFFTSquare : public mlir::OpRewritePattern { /// argument is the orchestrator of the sequence of rewrites. The pattern is /// expected to interact with it to perform any changes to the IR from here. mlir::LogicalResult - matchAndRewrite(SquareOp op, - mlir::PatternRewriter &rewriter) const override { + matchAndRewrite(SquareOp op, mlir::PatternRewriter &rewriter) const override { // Look through the input of the current downsampling. // mlir::Value squareOperand1_Rate = op.getOperand(1); mlir::Value squareOperand0_input = op.getInput(); @@ -252,63 +295,64 @@ struct SimplifyFFTSquare : public mlir::OpRewritePattern { if (!prev_FFT1DOp) return failure(); - //Replace fft1d with fft1dreal - DEBUG_PRINT_WITH_ARGS( squareOperand0_input) ; - DEBUG_PRINT_WITH_ARGS( "Going fr some") ; - DEBUG_PRINT_NO_ARGS() ; - mlir::Value prev_FFT1DOp_Operand = prev_FFT1DOp.getInput(); - auto fft1drealOp1 = rewriter.create(op.getLoc(), - prev_FFT1DOp_Operand ); + // Replace fft1d with fft1dreal + DEBUG_PRINT_WITH_ARGS(squareOperand0_input); + DEBUG_PRINT_WITH_ARGS("Going fr some"); + DEBUG_PRINT_NO_ARGS(); + mlir::Value prev_FFT1DOp_Operand = prev_FFT1DOp.getInput(); + auto fft1drealOp1 = + rewriter.create(op.getLoc(), prev_FFT1DOp_Operand); // DEBUG_PRINT_NO_ARGS(); - auto SquareOp1 = rewriter.create(op.getLoc(), fft1drealOp1); + auto SquareOp1 = rewriter.create(op.getLoc(), fft1drealOp1); rewriter.replaceOp(op, SquareOp1); return mlir::success(); } }; -struct SimplifyGainwZero: public mlir::OpRewritePattern{ - SimplifyGainwZero(mlir::MLIRContext *context) - : OpRewritePattern(context, 1) {} - - mlir::LogicalResult matchAndRewrite(GainOp op, - mlir::PatternRewriter &rewriter) const override { - - // - mlir::Value gainOp_operand1 = op.getOperand(1); - - //check if the value is zero - DEBUG_PRINT_NO_ARGS(); - dsp::ConstantOp constant_Op1 = gainOp_operand1.getDefiningOp(); +struct SimplifyGainwZero : public mlir::OpRewritePattern { + SimplifyGainwZero(mlir::MLIRContext *context) + : OpRewritePattern(context, 1) {} + + mlir::LogicalResult + matchAndRewrite(GainOp op, mlir::PatternRewriter &rewriter) const override { + + // + mlir::Value gainOp_operand1 = op.getOperand(1); + + // check if the value is zero + DEBUG_PRINT_NO_ARGS(); + dsp::ConstantOp constant_Op1 = + gainOp_operand1.getDefiningOp(); DenseElementsAttr DenseValueFrmgainOp = constant_Op1.getValue(); auto elements = DenseValueFrmgainOp.getValues(); float FirstValue = elements[0].getValueAsDouble(); - int64_t GainRate = (int64_t) FirstValue; + int64_t GainRate = (int64_t)FirstValue; - if(!GainRate==0) - return failure(); + if (!GainRate == 0) + return failure(); mlir::Value gainOp_operand0 = op.getOperand(0); - dsp::ConstantOp constant_Op0 = gainOp_operand0.getDefiningOp(); + dsp::ConstantOp constant_Op0 = + gainOp_operand0.getDefiningOp(); DenseElementsAttr InputValueFrmgainOp = constant_Op0.getValue(); int64_t inputSize = InputValueFrmgainOp.size(); - // Define the type of the tensor (tensor). - RankedTensorType tensorType = RankedTensorType::get({inputSize}, rewriter.getF64Type()); - - // Create a constant operation with the specified value and type. - DenseElementsAttr zerovalue = DenseElementsAttr::get(tensorType, 0.0); - Operation* constantOp = rewriter.create(op.getLoc(), zerovalue); + // Define the type of the tensor (tensor). + RankedTensorType tensorType = + RankedTensorType::get({inputSize}, rewriter.getF64Type()); + // Create a constant operation with the specified value and type. + DenseElementsAttr zerovalue = DenseElementsAttr::get(tensorType, 0.0); + Operation *constantOp = rewriter.create(op.getLoc(), zerovalue); rewriter.replaceOp(op, constantOp); return mlir::success(); - - } + } }; // Pseudo-code -// if operands of MulOp are coming from lowPassFIRFilter & hamming +// if operands of MulOp are coming from lowPassFIRFilter & hamming // then replace the MulOp with the symmetrical operation struct SimplifyFilterMulHamming : public mlir::OpRewritePattern { /// We register this pattern to match every dsp.downsampling in the IR. @@ -321,8 +365,7 @@ struct SimplifyFilterMulHamming : public mlir::OpRewritePattern { /// argument is the orchestrator of the sequence of rewrites. The pattern is /// expected to interact with it to perform any changes to the IR from here. mlir::LogicalResult - matchAndRewrite(MulOp op, - mlir::PatternRewriter &rewriter) const override { + matchAndRewrite(MulOp op, mlir::PatternRewriter &rewriter) const override { // Get the operands operation from MulFOp // check if op0 is Low/HighPassFIRFilterOp & op1 is HammingWindowOp // if this true then get the operands of op0 ie, Low/HighPassFIRFilterOp @@ -330,32 +373,35 @@ struct SimplifyFilterMulHamming : public mlir::OpRewritePattern { // mlir::Value squareOperand1_Rate = op.getOperand(1); mlir::Value mulOperand0_Lhs = op.getLhs(); mlir::Value mulOperand1_Rhs = op.getRhs(); - dsp::LowPassFIRFilterOp op_LowPassFIRFilterOp = mulOperand0_Lhs.getDefiningOp(); - dsp::HammingWindowOp op_HammingWindowOp = mulOperand1_Rhs.getDefiningOp(); + dsp::LowPassFIRFilterOp op_LowPassFIRFilterOp = + mulOperand0_Lhs.getDefiningOp(); + dsp::HammingWindowOp op_HammingWindowOp = + mulOperand1_Rhs.getDefiningOp(); DEBUG_PRINT_NO_ARGS(); // Inputs are LowPassFIRFilterOp && HammingWindowOp => If not, no match. if (!op_LowPassFIRFilterOp || !op_HammingWindowOp) return failure(); - //Replace fft1d with fft1dreal - DEBUG_PRINT_WITH_ARGS( mulOperand0_Lhs) ; - DEBUG_PRINT_WITH_ARGS( "SimplifyFilterMulHamming - ConditionMet") ; - DEBUG_PRINT_NO_ARGS() ; + // Replace fft1d with fft1dreal + DEBUG_PRINT_WITH_ARGS(mulOperand0_Lhs); + DEBUG_PRINT_WITH_ARGS("SimplifyFilterMulHamming - ConditionMet"); + DEBUG_PRINT_NO_ARGS(); mlir::Value LowPassFIRFilterOperand_wc = op_LowPassFIRFilterOp.getWc(); mlir::Value LowPassFIRFilterOperand_N = op_LowPassFIRFilterOp.getN(); - auto firFilterHammingOptimized = rewriter.create(op.getLoc(), - LowPassFIRFilterOperand_wc, LowPassFIRFilterOperand_N ); + auto firFilterHammingOptimized = + rewriter.create( + op.getLoc(), LowPassFIRFilterOperand_wc, LowPassFIRFilterOperand_N); DEBUG_PRINT_NO_ARGS(); - + rewriter.replaceOp(op, firFilterHammingOptimized); return mlir::success(); } }; // Pseudo-code -// if operands of MulOp are coming from highPassFIRFilter & hamming +// if operands of MulOp are coming from highPassFIRFilter & hamming // then replace the MulOp with the symmetrical operation struct SimplifyHighPassFIRHamming : public mlir::OpRewritePattern { /// We register this pattern to match every dsp.downsampling in the IR. @@ -368,8 +414,7 @@ struct SimplifyHighPassFIRHamming : public mlir::OpRewritePattern { /// argument is the orchestrator of the sequence of rewrites. The pattern is /// expected to interact with it to perform any changes to the IR from here. mlir::LogicalResult - matchAndRewrite(MulOp op, - mlir::PatternRewriter &rewriter) const override { + matchAndRewrite(MulOp op, mlir::PatternRewriter &rewriter) const override { // Get the operands operation from MulFOp // check if op0 is Low/HighPassFIRFilterOp & op1 is HammingWindowOp // if this true then get the operands of op0 ie, Low/HighPassFIRFilterOp @@ -377,38 +422,43 @@ struct SimplifyHighPassFIRHamming : public mlir::OpRewritePattern { // mlir::Value squareOperand1_Rate = op.getOperand(1); mlir::Value mulOperand0_Lhs = op.getLhs(); mlir::Value mulOperand1_Rhs = op.getRhs(); - dsp::HighPassFIRFilterOp op_HighPassFIRFilterOp = mulOperand0_Lhs.getDefiningOp(); - dsp::HammingWindowOp op_HammingWindowOp = mulOperand1_Rhs.getDefiningOp(); + dsp::HighPassFIRFilterOp op_HighPassFIRFilterOp = + mulOperand0_Lhs.getDefiningOp(); + dsp::HammingWindowOp op_HammingWindowOp = + mulOperand1_Rhs.getDefiningOp(); DEBUG_PRINT_NO_ARGS(); // Inputs are HighPassFIRFilterOp && HammingWindowOp => If not, no match. if (!op_HighPassFIRFilterOp || !op_HammingWindowOp) return failure(); - //Replace fft1d with fft1dreal - DEBUG_PRINT_WITH_ARGS( mulOperand0_Lhs) ; - DEBUG_PRINT_WITH_ARGS( "SimplifyHighPassFIRHamming - ConditionMet") ; - DEBUG_PRINT_NO_ARGS() ; + // Replace fft1d with fft1dreal + DEBUG_PRINT_WITH_ARGS(mulOperand0_Lhs); + DEBUG_PRINT_WITH_ARGS("SimplifyHighPassFIRHamming - ConditionMet"); + DEBUG_PRINT_NO_ARGS(); mlir::Value HighPassFIRFilterOperand_wc = op_HighPassFIRFilterOp.getWc(); mlir::Value HighPassFIRFilterOperand_N = op_HighPassFIRFilterOp.getN(); - auto highPassFIRHammingOptimized = rewriter.create(op.getLoc(), - HighPassFIRFilterOperand_wc, HighPassFIRFilterOperand_N ); + auto highPassFIRHammingOptimized = + rewriter.create( + op.getLoc(), HighPassFIRFilterOperand_wc, + HighPassFIRFilterOperand_N); DEBUG_PRINT_NO_ARGS(); - + rewriter.replaceOp(op, highPassFIRHammingOptimized); return mlir::success(); } }; -//Pseudo-Code -//Find FIRFilterResponse & FIRFilterHammingOptimized & operation - // result1 = dsp.FIRFilterHammingOptimized(input1, rate1) //filter and hamming - // result2 = dsp.FIRFilterResponse(result1, rate2) //FilterResponse -// For above pattern , replace dsp.FIRFilterResponse with FIRFilterResSymmOptimized - // result1 = dsp.FIRFilterHammingOptimized(input1, rate1) - // result2 = dsp.FIRFilterResSymmOptimized(result1, rate2) -struct SimplifyFIRFilterRespnseWithSymmFilter : public mlir::OpRewritePattern { +// Pseudo-Code +// Find FIRFilterResponse & FIRFilterHammingOptimized & operation +// result1 = dsp.FIRFilterHammingOptimized(input1, rate1) //filter and hamming +// result2 = dsp.FIRFilterResponse(result1, rate2) //FilterResponse +// For above pattern , replace dsp.FIRFilterResponse with +// FIRFilterResSymmOptimized result1 = dsp.FIRFilterHammingOptimized(input1, +// rate1) result2 = dsp.FIRFilterResSymmOptimized(result1, rate2) +struct SimplifyFIRFilterRespnseWithSymmFilter + : public mlir::OpRewritePattern { /// We register this pattern to match every dsp.downsampling in the IR. /// The "benefit" is used by the framework to order the patterns and process /// them in order of profitability. @@ -422,37 +472,41 @@ struct SimplifyFIRFilterRespnseWithSymmFilter : public mlir::OpRewritePattern(); + dsp::FIRFilterHammingOptimizedOp prev_FIRFilterSymmOp = + Operand1_forFIRFilterResp.getDefiningOp(); // Input defined by another downsampling? If not, no match. - if (!prev_FIRFilterSymmOp){ + if (!prev_FIRFilterSymmOp) { return failure(); } // create FIRFilterHammingOptimizedOp with current operands - DEBUG_PRINT_WITH_ARGS("Going for FIRFilterresponse Opt when the operand1 is a symmetric filter"); - - auto firFilterResSymmOptimizedOp = rewriter.create(op.getLoc(), - Operand0_forFIRFilterResp , Operand1_forFIRFilterResp); + DEBUG_PRINT_WITH_ARGS("Going for FIRFilterresponse Opt when the operand1 " + "is a symmetric filter"); + + auto firFilterResSymmOptimizedOp = + rewriter.create( + op.getLoc(), Operand0_forFIRFilterResp, Operand1_forFIRFilterResp); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); rewriter.replaceOp(op, firFilterResSymmOptimizedOp); return mlir::success(); } }; -//Pseudo code: -// if the FFT1DRealOp & FFT1DImgOp has same input then replace them with single -// %4 = "dsp.fft1dreal"(%3) : (tensor<10xf64>) -> tensor<10xf64> -// %5 = "dsp.fft1dimg"(%3) : (tensor<10xf64>) -> tensor<10xf64> -// replace with %4, %5 = "dsp.fft1d"(%3) : (tensor<10xf64>) -> (tensor<10xf64 , tensor<10xf64)> +// Pseudo code: +// if the FFT1DRealOp & FFT1DImgOp has same input then replace them with single +// %4 = "dsp.fft1dreal"(%3) : (tensor<10xf64>) -> tensor<10xf64> +// %5 = "dsp.fft1dimg"(%3) : (tensor<10xf64>) -> tensor<10xf64> +// replace with %4, %5 = "dsp.fft1d"(%3) : (tensor<10xf64>) -> (tensor<10xf64 , +// tensor<10xf64)> // -// Define the canonicalization pattern. +// Define the canonicalization pattern. struct SimplifyFFTRealAndImg : public OpRewritePattern { SimplifyFFTRealAndImg(MLIRContext *context) : OpRewritePattern(context, /*benefit=*/1) {} @@ -464,14 +518,15 @@ struct SimplifyFFTRealAndImg : public OpRewritePattern { if (!nextOp || !isa(nextOp)) return failure(); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); auto imgOp = cast(nextOp); if (realOp.getInput() != imgOp.getInput()) return failure(); // Replace the two operations with the combined FFT1D operation. - DEBUG_PRINT_NO_ARGS() ; - auto combinedOp = rewriter.create(realOp.getLoc(), realOp.getInput()); + DEBUG_PRINT_NO_ARGS(); + auto combinedOp = + rewriter.create(realOp.getLoc(), realOp.getInput()); rewriter.replaceOp(realOp, combinedOp.getResult(0)); rewriter.replaceOp(imgOp, combinedOp.getResult(1)); @@ -479,15 +534,16 @@ struct SimplifyFFTRealAndImg : public OpRewritePattern { } }; - -//Pseudo-Code -//Find FIRFilterResponse & reverseInput - // %1 = "dsp.reverseInput"(%0) : (tensor<4xf64>) -> tensor<*xf64> - // %2 = "dsp.FIRFilterResponse"(%0, %1) : (tensor<4xf64>, tensor<*xf64>) -> tensor<*xf64> -// For above pattern , replace dsp.FIRFilterResponse with FIRFilterYSymmOptimized - // %1 = "dsp.reverseInput"(%0) - // result2 = dsp.FIRFilterYSymmOptimized(result1, rate2) -struct SimplifyFilterRespX_ReverseXYSymmFilter : public mlir::OpRewritePattern { +// Pseudo-Code +// Find FIRFilterResponse & reverseInput +// %1 = "dsp.reverseInput"(%0) : (tensor<4xf64>) -> tensor<*xf64> +// %2 = "dsp.FIRFilterResponse"(%0, %1) : (tensor<4xf64>, tensor<*xf64>) -> +// tensor<*xf64> +// For above pattern , replace dsp.FIRFilterResponse with +// FIRFilterYSymmOptimized %1 = "dsp.reverseInput"(%0) result2 = +// dsp.FIRFilterYSymmOptimized(result1, rate2) +struct SimplifyFilterRespX_ReverseXYSymmFilter + : public mlir::OpRewritePattern { /// We register this pattern to match every dsp.downsampling in the IR. /// The "benefit" is used by the framework to order the patterns and process /// them in order of profitability. @@ -501,33 +557,36 @@ struct SimplifyFilterRespX_ReverseXYSymmFilter : public mlir::OpRewritePattern(); + dsp::ReverseInputOp prev_ReverseOp = + Operand1_forFIRFilterResp.getDefiningOp(); // Operand1 defined by another ReverseOp? If not, no match. - if (!prev_ReverseOp){ + if (!prev_ReverseOp) { return failure(); } // create FIRFilterYSymmOptimizedOp with current operands - DEBUG_PRINT_WITH_ARGS("Going for FIRFilterResponse Opt when the operand1 is a ReverseInputOp"); - - auto firFilterResYSymmOptimizedOp = rewriter.create(op.getLoc(), - Operand0_forFIRFilterResp , Operand1_forFIRFilterResp); + DEBUG_PRINT_WITH_ARGS("Going for FIRFilterResponse Opt when the operand1 " + "is a ReverseInputOp"); + + auto firFilterResYSymmOptimizedOp = + rewriter.create( + op.getLoc(), Operand0_forFIRFilterResp, Operand1_forFIRFilterResp); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); rewriter.replaceOp(op, firFilterResYSymmOptimizedOp); return mlir::success(); } }; -//Pseudo code: -// if the input of FFT1DRealOp = FIRFilterYSymmOptimizedOp then replace it with FFT1DRealSymmOp -// Define the canonicalization pattern. +// Pseudo code: +// if the input of FFT1DRealOp = FIRFilterYSymmOptimizedOp then replace it +// with FFT1DRealSymmOp Define the canonicalization pattern. struct SimplifyFFTRealAtInputRealSymm : public OpRewritePattern { SimplifyFFTRealAtInputRealSymm(MLIRContext *context) : OpRewritePattern(context, /*benefit=*/1) {} @@ -536,27 +595,28 @@ struct SimplifyFFTRealAtInputRealSymm : public OpRewritePattern { PatternRewriter &rewriter) const override { // Check if there is a corresponding FFT1DImgOp with the same input. mlir::Value fftOperand_input = Op.getInput(); - dsp::FIRFilterYSymmOptimizedOp op_FIRFilterYSymmOptimizedOp = fftOperand_input.getDefiningOp(); - + dsp::FIRFilterYSymmOptimizedOp op_FIRFilterYSymmOptimizedOp = + fftOperand_input.getDefiningOp(); + if (!op_FIRFilterYSymmOptimizedOp) return failure(); - DEBUG_PRINT_NO_ARGS() ; - + DEBUG_PRINT_NO_ARGS(); // Replace the two operations with the combined FFT1D operation. - auto fft1dRealSymmOp = rewriter.create(Op.getLoc(), Op.getInput()); - DEBUG_PRINT_NO_ARGS() ; + auto fft1dRealSymmOp = + rewriter.create(Op.getLoc(), Op.getInput()); + DEBUG_PRINT_NO_ARGS(); rewriter.replaceOp(Op, fft1dRealSymmOp.getResult()); // rewriter.replaceOp(Op, fft1dRealSymmOp); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); return success(); } }; -//Pseudo code: -// if the input of FFT1DImgOp = FIRFilterYSymmOptimizedOp then replace it with FFT1DImgConjSymmOp -// Define the canonicalization pattern. +// Pseudo code: +// if the input of FFT1DImgOp = FIRFilterYSymmOptimizedOp then replace it with +// FFT1DImgConjSymmOp Define the canonicalization pattern. struct SimplifyFFTImgAtInputRealSymm : public OpRewritePattern { SimplifyFFTImgAtInputRealSymm(MLIRContext *context) : OpRewritePattern(context, /*benefit=*/1) {} @@ -565,62 +625,69 @@ struct SimplifyFFTImgAtInputRealSymm : public OpRewritePattern { PatternRewriter &rewriter) const override { // Check if there is a corresponding FFT1DImgOp with the same input. mlir::Value fftOperand_input = Op.getInput(); - dsp::FIRFilterYSymmOptimizedOp op_FIRFilterYSymmOptimizedOp = fftOperand_input.getDefiningOp(); - + dsp::FIRFilterYSymmOptimizedOp op_FIRFilterYSymmOptimizedOp = + fftOperand_input.getDefiningOp(); + if (!op_FIRFilterYSymmOptimizedOp) return failure(); - DEBUG_PRINT_NO_ARGS() ; - + DEBUG_PRINT_NO_ARGS(); // Replace the two operations with the combined FFT1D operation. - - auto fft1dImgConjSymmOp = rewriter.create(Op.getLoc(), Op.getInput()); - DEBUG_PRINT_NO_ARGS() ; + + auto fft1dImgConjSymmOp = + rewriter.create(Op.getLoc(), Op.getInput()); + DEBUG_PRINT_NO_ARGS(); // rewriter.replaceOp(Op, fft1dImgConjSymmOp.getResult()); rewriter.replaceOp(Op, fft1dImgConjSymmOp); - DEBUG_PRINT_NO_ARGS() ; + DEBUG_PRINT_NO_ARGS(); return success(); } }; +// Pseudo-Code +// Find lmsFIlter with gain operation +// result1 = lmsFilterResponse(noisy_sig, clean_sig, mu, filterSize); +// result2 = gain(result1, G1) +// result2 will be now lmsFilterResponse(noisy_sig, clean_sig, mu*g1, +// filterSize); replaceOp +struct SimplifyLMSFilterResponsewithGain + : public mlir::OpRewritePattern { + SimplifyLMSFilterResponsewithGain(mlir::MLIRContext *context) + : OpRewritePattern(context, 1) {} -//Pseudo-Code -//Find lmsFIlter with gain operation - // result1 = lmsFilterResponse(noisy_sig, clean_sig, mu, filterSize); - // result2 = gain(result1, G1) - // result2 will be now lmsFilterResponse(noisy_sig, clean_sig, mu*g1, filterSize); - // replaceOp -struct SimplifyLMSFilterResponsewithGain: public mlir::OpRewritePattern{ - SimplifyLMSFilterResponsewithGain(mlir::MLIRContext *context) - : OpRewritePattern(context, 1) {} - - mlir::LogicalResult matchAndRewrite(GainOp op, - mlir::PatternRewriter &rewriter) const override { - - mlir::Value gainOp_operand0 = op.getOperand(0); - - LMSFilterResponseOp prev_LMSFilterResponseOp = gainOp_operand0.getDefiningOp(); - - if(!prev_LMSFilterResponseOp) - return failure(); + mlir::LogicalResult + matchAndRewrite(GainOp op, mlir::PatternRewriter &rewriter) const override { + + mlir::Value gainOp_operand0 = op.getOperand(0); + + LMSFilterResponseOp prev_LMSFilterResponseOp = + gainOp_operand0.getDefiningOp(); + + if (!prev_LMSFilterResponseOp) + return failure(); - mlir::Value gainOp_operand1 = op.getOperand(1); - mlir::Value prev_LMSFilterResponseOp_0 = prev_LMSFilterResponseOp.getOperand(0); - mlir::Value prev_LMSFilterResponseOp_1 = prev_LMSFilterResponseOp.getOperand(1); - mlir::Value prev_LMSFilterResponseOp_mu = prev_LMSFilterResponseOp.getOperand(2); - mlir::Value prev_LMSFilterResponseOp_3 = prev_LMSFilterResponseOp.getOperand(3); - - //create mul op - auto mulOp = rewriter.create(op.getLoc(), prev_LMSFilterResponseOp_mu, gainOp_operand1); - auto newLMSFilterResponseOp = rewriter.create(op.getLoc(), - prev_LMSFilterResponseOp_0, prev_LMSFilterResponseOp_1, mulOp.getResult(), prev_LMSFilterResponseOp_3); - - //Repalce the use of original gain operation with this newGainOp + mlir::Value gainOp_operand1 = op.getOperand(1); + mlir::Value prev_LMSFilterResponseOp_0 = + prev_LMSFilterResponseOp.getOperand(0); + mlir::Value prev_LMSFilterResponseOp_1 = + prev_LMSFilterResponseOp.getOperand(1); + mlir::Value prev_LMSFilterResponseOp_mu = + prev_LMSFilterResponseOp.getOperand(2); + mlir::Value prev_LMSFilterResponseOp_3 = + prev_LMSFilterResponseOp.getOperand(3); + + // create mul op + auto mulOp = rewriter.create( + op.getLoc(), prev_LMSFilterResponseOp_mu, gainOp_operand1); + auto newLMSFilterResponseOp = rewriter.create( + op.getLoc(), prev_LMSFilterResponseOp_0, prev_LMSFilterResponseOp_1, + mulOp.getResult(), prev_LMSFilterResponseOp_3); + + // Repalce the use of original gain operation with this newGainOp rewriter.replaceOp(op, newLMSFilterResponseOp.getResult()); return mlir::success(); - - } + } }; // =================================== @@ -636,26 +703,26 @@ struct SimplifyLMSFilterResponsewithGain: public mlir::OpRewritePattern{ /// Register our patterns as "canonicalization" patterns on the TransposeOp so /// that they can be picked up by the Canonicalization framework. void FFT1DImgOp::getCanonicalizationPatterns(RewritePatternSet &results, - MLIRContext *context) { + MLIRContext *context) { if (getEnableCanonicalOpt()) { - results.add(context); + results.add< // SimplifyFFTRealAndImg, + SimplifyFFTImgAtInputRealSymm>(context); } } void FFT1DRealOp::getCanonicalizationPatterns(RewritePatternSet &results, - MLIRContext *context) { + MLIRContext *context) { if (getEnableCanonicalOpt()) { - results.add(context); + results.add< // SimplifyFFTRealAndImg, + SimplifyFFTRealAtInputRealSymm>(context); } } -void FIRFilterResponseOp::getCanonicalizationPatterns(RewritePatternSet &results, - MLIRContext *context) { +void FIRFilterResponseOp::getCanonicalizationPatterns( + RewritePatternSet &results, MLIRContext *context) { if (getEnableCanonicalOpt()) { - results.add(context); + results.add(context); } } @@ -673,7 +740,6 @@ void SquareOp::getCanonicalizationPatterns(RewritePatternSet &results, } } - /// Register our patterns as "canonicalization" patterns on the TransposeOp so /// that they can be picked up by the Canonicalization framework. @@ -701,11 +767,19 @@ void DelayOp::getCanonicalizationPatterns(RewritePatternSet &results, } } -void GainOp::getCanonicalizationPatterns(RewritePatternSet &results, - MLIRContext *context) { +void GainOp::getCanonicalizationPatterns(RewritePatternSet &results, + MLIRContext *context) { // results.add(context); if (getEnableCanonicalOpt()) { - results.add(context); + results.add( + context); + } +} + +void MeanOp::getCanonicalizationPatterns(RewritePatternSet &results, + MLIRContext *context) { + if (getEnableCanonicalOpt()) { + results.add(context); } } diff --git a/mlir/test/Examples/DspExample/dsp_biomedical.py b/mlir/test/Examples/DspExample/dsp_biomedical.py new file mode 100644 index 000000000000..cbb8ca24af17 --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_biomedical.py @@ -0,0 +1,64 @@ +def main() { + var input_signal = [0.09391447818349865639, -0.64124217719704934559, -0.62692484054281838457, 0.01275379584596916457, 0.54796006228603910682, -0.78928997929850563953, 0.66584321589266248775, -0.27261286333223494482, 0.37735143885667149499, -0.04032548100483641929, 0.34760746338597903193, 0.08750261364008603271, -0.39336237038669169541, -0.13992208725293370231, -0.03000789137945208962, -0.66949358566426153683, 0.39060269895958293906, -0.55498474667461572540, -0.88563294524087721182, 0.00673558649672073573, -0.29968009344275092776, -0.37167151746433341186, 1.07130673806743748067, 0.38586275933191216403, 0.77598317195055210860, -0.79294992706261857585, -0.78001185531780892202, 0.11210388464987396107, 1.05424296277589624182, 0.76981354956633185616, 0.25523734290301064442, -0.33809204260204811510, 0.95315166684373753281, 0.26008564468881201215, 0.58876648378181939414, -0.28087402961904706089, 1.19144681461698231928, 0.29609750057489769848, 0.57464455208245968088, 1.02435938662426684331, -0.45253993595583219545, -0.95427439150047754790, 0.46238011484697150166, 0.63183510683658594687, 0.53634500920042937899, -0.03109995126536169163, 0.89267964646365993708, -0.82939704833142924301, -0.85373890824349696338, 0.02342685250975734546, 1.57212071365485273056, 0.87342602291143256465, 0.08831370586921122312, -0.00522271222803732948, 1.03672540114726974281, 1.20585550366192695293, 0.41987842758305826685, -0.04249863204181153487, 0.52099665030295705392, 0.75058470154504597360, 0.58531608098388143713, -0.00357873971817451730, 0.59643153109790347788, -0.30127231636450024777, -0.68453395622814872112, 0.09085843420893024991, 0.89552565900418334977, 1.03352745353178310239, 0.03549419301161099716, 0.13406804741697736505, 1.07174666641970217640, 0.44930092518113484701, 0.82884702462989623406, 0.93881425701790854887, 1.07278612567036213399, 1.05200572436050299174, 1.41523562096756005957, -0.30215587638575314156, 0.99853990681816440222, 0.22830091801084728687, 0.69366683328045930512, 0.54950026084627878742, -0.08387356113840860328, 1.35760915527749470577, -0.35237766809126924716, 1.25736877180850781244, 0.60846883126887585380, 0.23451043722395237268, 0.86880181953028123853, 0.36388595731515288589, -0.41257475901782725280, 0.71332566240772687927, 0.01032871329030404794, 0.66221095583151512454, -0.16580117687027862150, 1.96599902259062897514, 0.86107091313818795442, 1.11338758436425022680, 0.16692826424335627244, 0.24917397082510700512, 1.18135909673451511281, -0.20480523217480139930, 0.79124737211770845846, 0.74473823029637753468, 0.92235363553196125785, 0.04658385900091366327, 0.37422271257771638764, 0.30246172702415369260, 0.24478546810998436101, 2.13083118288844275412, 0.40090204635422022061, 0.57312477551478979709, 1.00684232460216649407, 0.96751420028080148406, -0.00274662496819366719, -0.08841483691441620607, 0.27238515446718297719, 0.53945213787503276670, 0.35216609373673479766, 1.00940046570138730608, 0.18925861730019849505, 0.74898446646354943645, 0.88221219656386962171, 0.66230951624880241013, 0.84452427375177152591, 0.07435668420638441578, -0.40286633344518696376, 1.07555891840149397964, 0.27408743073695868153, 0.70833585831952705636, -0.01359718554712519012, 0.64373137902516897046, 0.18041402857681132499, 1.07600669754191957672, 0.01254815239008155725, 0.50956270613537224534, 0.26091430357024164577, 0.40485342256217338175, 0.60233666990861567392, -0.06443275514685653516, 0.72206666860667678698, 0.47224775392542356345, 0.56810948893010992933, 0.63542653848075181244, 0.85181893332911007288, 1.52499833767253267780, 1.20240311020337342640, 1.75257181193744404624, 1.08048609612313439854, 1.67772856282504001513, 0.21610969109458477710, 0.71221576711241718627, 0.79498695985871081771, 1.35693571799292733715, 0.66977105345721210661, 0.34462686256106334071, 1.14407258429036895464, -0.02033206662019215738, 0.10743833106353362083, 0.49076436204561485477, 0.04174717139575079283, 0.69375763570386850265, 0.43035171410453459950, -0.16812265217504418491, 1.33093470069918273779, 0.94152996852788573001, 0.27877102412528109721, 0.82036503853332320979, -0.18371902877937484888, 0.67018735843625520943, 1.11429317127647697916, 0.07638535349681990771, 1.03073958473393467727, 0.42073128705144374750, 0.30674532050030628438, 0.15055276546732965226, 1.18099050232762214918, 0.53042461540071383919, 0.98830245141143269194, 0.89188839391060403816, 0.06308057398534394622, 0.30607413082411127903, 1.51947758474885685764, 0.46117930529971040698, 0.21490151600731086101, 1.16866646449455546808, 0.38310345186209809887, 0.99245619194688372566, 0.29338498199476659156, 1.04983466056790231136, -0.29417527307010643556, 0.42320932380766290715, 0.91148161047995879258, 0.89083923211745941195, 0.57708418406216699381, 0.13524234825948835970, 1.38350585633078604353, 0.35012429855462162687, 0.12492784301101086952, 0.86948955446786446721, -0.23363142145282583817, 0.13006214044203068259, -0.04237612600026541276, 1.29637329899679976108, 0.06511784322398822544, -0.05396327078496410135, 0.08649095343532944846, -0.03019685673215311006, 0.12869637596860863948, 0.87757529019843394380, 0.66307910175066420155, 0.40068018218527812202, 0.74495813212544859105, 0.52641988883382118125, 0.04587903441341781297, 1.55445277963515593456, 0.21795354956207602104, 0.66220727253425359926, 1.19330925522208097789, 0.56162748354815572593, -0.03637749633366196833, -0.11473777255928022112, 0.49850689042882989632, 0.48450914417960433411, 0.48006054513872209455, -0.09103515636425568713, -0.44671221471309074946, 0.03883974102923495875, -0.09902453878002623333, 0.22665978819822624191, -0.14781589853391158274, 0.12814687056024970380, 0.74779135850382782991, 0.28311537345191273296, 0.26213848869667094599, -0.49957813537844081297, 0.21023316934907945730, 0.64669440801895716753, 0.82290947709401607302, 0.68280624182231619557, -1.00393170190676528364, 0.67513776050600649015, 0.69036228407902866522, 0.11649958830362899809, 0.69222583131505177612, -0.09959226938600301837, -0.18366668033768301926, -0.34718225118477052948, 0.86918378471540724828, 0.10457675545881890689, -0.08112797434002502750, -0.30121694535291232420, -0.36331682540476500920, -0.01402990854082046546, 0.34988315584042600781, 0.47426224451741366694, -0.18238113027436977931, 0.08356985576051237374, -0.90990068695001502519, 0.10139169785108301247, -0.38097649980263087466, 0.07659981868350396184, -0.13459034341996067852, -0.39976968194571321780, -0.15335282338983952100, -0.96380372499736699510, -0.08126735377012267447, -0.66339548960312155756, 0.40780227006908020826, 0.21545625563132370095, -0.81790616421192274821, -0.41973790158210566581, -0.21577637826951218125, -0.31702579590228352480, -0.53829852397056754398, 0.13593242682932568655, 0.16686764477995624789, -0.27671306106853527096, 0.23329674564842900475, -0.64777511546045407620, 0.78655689880155288396, -0.09916109332330380610, -1.18157251114269135073, -0.53737476591643684731, 0.63558511687284013902, 0.12734842675935503786, -0.29440659726853202205, -0.35299739792147272199, -0.83958229257499272791, -0.41558192538630339685, -0.40064488065156012020, -0.65295643709666051357, -0.22025448222512514218, 0.56061229515359678466, -1.36527202576251771582, -0.29156694772510516334, -0.88987509089879268664, 0.01180144258537074720, 0.26937606131898178541, -1.25642984477524022324, -0.95022992934930905484, -0.62239154318497691420, -0.38126083433599333716, -0.73552201540328532303, -0.02369511384384270780, -0.14674273530927686715, -0.20938454299185485508, 0.21499373480923594792, 0.03746983275381166045, 0.20980920849842521481, -0.54573564261711282608, -1.11676615004888324378, -0.88545084578605559589, -0.35611253732666503513, -0.83390145297159468107, 0.48746053767095731679, 0.03577465011813768525, 0.00064389610326015534, -1.00140152908365243078, 0.06069516585161383038, -0.37820149287207854849, -1.19940629878695803612, 0.05636693293195671473, -0.01751036819897971997, 0.31880741543437030039, -0.71248848916092244465, -1.28804130204737976584, -1.54816235848471395187, 0.54880538881778428983, -1.46043065834484209198, 0.69559126826421291412, -0.21283142192574650009, 0.16289665298283795281, -0.47662645479827542294, -2.25434984166571927844, -0.88873151768656477589, -0.59401620321138248748, -0.81795366125250534850, 0.47629367630143915058, -0.35857899725000974556, 0.18517240533072298891, -0.49564855820955827648, -0.81292404086702330090, -0.32544371466954902239, -0.44049054672524179388, 0.30361834048874125624, -0.48822245864726809828, -0.89163372212238756198, -1.04082507957091241479, -0.68596155648419088191, -0.01242381969128125707, -0.32096073816043774185, 0.20368801378914525024, -1.41738513979201719017, 0.29666789496311263274, -0.31327344976759824435, -0.92424562580202329620, -0.26613836235289278775, -0.73971838195852235298, -0.90555462004205422577, -0.59034415382356575375, -0.89232121241320894178, -0.30538880163070247775, -0.87041535127760816248, -1.19016738674721911373, -1.41287769516728523023, -0.50391634614517799573, -0.36750881117098466166, -0.63368398514766988150, -0.60479368924704279831, -0.17734427699328669803, -0.81015618196224004066, -1.21869277783517127034, -0.81623748824757369480, -1.73566455574227251901, -0.73717351758885385493, -0.82531839118386907117, -1.22154483751334375086, -1.24855964808297348334, -0.44002199156843546657, -1.22448876377036564023, -0.22977659797195010283, -0.62096768146173564329, -0.31639236618947585988, 0.25187626212062874664, -0.05298191473342050983, 0.39146477260910061968, -0.68455484858757209565, -1.24844962881476728711, -1.46467419566822787402, -1.33500745061883518972, -0.90838296252963157684, -0.02397821508985376049, -0.61091235595626802724, -1.00514814907315286341, -0.10597556387550471912, -0.96061128866199330112, -0.71216561614460516250, -0.57519660622536372685, 0.44731217162472369786, -0.67635375106683826019, -0.30873008080153191424, -0.59738703293825223817, -0.20080833788644053550, -0.73784628421693565503, 0.26333665419794693996, -0.87165557031953389000, -1.46836077827615874014, -0.59544305595777169415, -0.69719097849295363911, -0.26503783515772022517, -0.09828769484448379723, -1.15363125001043331785, 0.48624669319997126937, 0.16109926301438781682, -1.15772019929848890563, -0.78430583908825224349, -0.93514655927017831250, -0.97152047788574935616, -0.62783203634544915150, 0.51877683006390518106, -0.63313658485377166674, -0.18544261249061677610, -0.44739587023791765885, -0.39615786539984731984, -0.50967405481174798965, -0.87069351788568827288, -0.20651260136271382128, -0.59619216152806697728, 0.10837015868786969985, -0.46777181362743069926, -0.68654777888832185795, -1.31268762105612846192, -0.08497503165402225944, -0.79088415747031937109, -0.34625378271578322043, -0.60146657373573242378, -0.21891836744752238286, -0.35167484038966401183, 0.18207642738929680215, 0.13492392088026150532, -0.63194331858864094542, -0.55971156886414497045, 0.09916598098646317583, -0.62479185334231746030, -0.43798309168051197116, -0.37637712780106136190, -0.24987530928236750816, -0.26614567524735260884, -0.32561367632572751907, -0.34756741137191243807, 0.24780539951632141182, 0.06774691059140852678, -0.44253184142155788106, -1.03969481897534365977, 0.31472695855053045566, -0.41071929870994539069, -0.24836871646688729598, -0.23909019647305618239, -0.80411663944202338428, -0.04676479567365990353, -0.50618658450337150612, 0.47565640770866507436, -0.85575052766018355754, 0.16634943731885259988, 0.04267977706960690565, -0.19243319151588628158, -0.09119799423208369005, 0.03689509285176600928, -0.16856707697524808687, 0.33190162337593343000, -0.02069330709524677681, 0.57947880919016048207, 0.33866322403833648558, 0.33204546002693191564, 0.72386485601748906671, -0.11025426153583406386, -0.27637873310954774730, -0.65127404759752138830, -0.02634955325616653021, 0.03698357634568788521, 0.12154623620586629817, -0.65698312732799490909, 0.23389685342379037958, -0.06842344392905776174, -0.12181049313552021196, 0.16792284051384387400, -0.54731101866909803721, -0.99040774674814424650, 0.45100178560647108394, -0.34767172826770059713, 0.13675660748668322375, -0.29145016654095212694, 0.52255676395979155924, -0.23657162543300502833, -0.29056485455079772429, 0.09959233359893603088, 0.25138937361859053921, -0.31911367026500114807, -0.02309901535220839838, -0.57136546661336995445, 0.32965074638705621490, -0.03020931513326359855, -0.46786731483690480182, 0.35501428706646243061, 0.15231646898751408092, -0.31895366568708266586, 0.15129929704293607062, -0.65603816215470944773, 0.75352720782284587919, -0.25418491906215479048, 0.29370172539024125280, 0.03072742359411854973, -0.23518486234546398528, 0.26393147004952749457, 0.37146726223233272890, -0.04605017166034018616, -0.11026459070937078000, 0.65285145882287509700, 0.24608731352028911932, 0.16128893313020575828, 0.32767366619754650925, -0.11556779579503723143, 1.22094072723894697674, 0.03288541195173211307, -0.38220816145034663958, 0.15930392061996195840, -0.46343697010506323952, -0.37930264910590577587, 0.02049424196119797870, 0.49648268963256059205, 1.16901458762458365648, 0.76381001003125315041, 1.05059169075396674486, 0.58274384296521430926, 1.29957383063246645349, -0.39081944153130132591, -0.00828338870021660956, 0.67888149100840178285, 0.16805235729229656139, 0.30195193985438301132, 0.63050119820604666465, 0.63733704537240387644, -0.57749374530399000260, -0.35385722793035584433, 0.75668345869658870662, 0.05556119566548189947, 0.60285281734999629499, 0.15614089160276509172, 0.96114926205869277709, 0.93851453738296464202, 0.40280429267986600506, 0.85497180310963405425, 0.74969166145720367300, 0.27537987923550882874, -0.23647886335430295945, 1.65182193613133287968, 0.63383095095227726112, 0.28560325628648525198, -0.30990561385303128095, -0.10786250782120571090, 0.95908988385452542680, 0.37736624132653573405, 1.09052721780124262096, -0.10660060041120339447, -0.32892447107336503231, 0.60817493296617686216, 0.43021505517392788498, 1.61821035276514990642, 0.54477951722555317815, 0.35666666174014777235, 0.78430076702777129505, 0.16815778845823142751, 0.11874999127484686667, -0.10161527010270526850, 1.67704098849149940698, 0.32471279022608268150, 0.97906593283073506395, 0.91398049191643604416, 0.21323403690008690958, -0.27088573294525453150, 0.74837498606452812488, 0.58580153921460897948, 1.07919759964903771987, 0.96527893727244351574, 0.65470892361208399279, 1.06094344481275126846, 0.47974289775389694634, -0.57465329651194774208, -0.06726551645793121814, -0.40384525149856453918, 1.13319172168718207416, 0.81854368092370854981, 0.43107006058438906981, 1.94257837461359317288, 0.39715216454709162397, -0.31250108601998904945, 0.50068921075657857322, 0.51267154616787924404, 0.72290581264439679465, 0.08486945858838118584, -0.29663651966683368677, 0.85171121658293813539, 1.25979348611680652681, 1.53508784300984890692, 0.70814832538912309001, 1.40882928623625147679, 0.90698698962461810069, 0.06520128622007892449, 0.60243418152429972778, 1.08521232950244739257, 0.43887975755868213756, 0.09468144750091883610, 0.43382060006497535909, -0.32870353696341714222, -0.72342562422811018674, 0.10286397936580260470, -0.15393787968756500462, 0.51179539391266559711, -0.76212200713161559751, 0.56815672534823347117, 1.16462610045649039847, 0.77420827708620698626, 0.47295443544213877640, 0.66362198552546070029, 0.90618235864173979355, 0.20587654685706341384, 1.58637571532921528572, 0.50304274480733468522, 0.01932089536424286269, 1.47509003244507819197, 0.88361088382580499356, 0.68279106244174592177, 0.56418963565966717688, 0.70457189924840935014, -0.17819825776596964761, -0.11070632123069457098, 0.11371389598443987223, 0.71008653044706404600, 0.19359997299505299351, 1.01412949649275740960, 1.20064450529159705283, 0.68307093713609878805, 0.44608459653411147716, 0.40939474607096004721, -0.21849120511986164228, 1.07705145700775362094, 1.32399648254871982189, 0.48518921060314901261, 0.26290570979670868645, 1.09455144274684057493, 0.66329832939853605733, 0.51502487241697270104, 1.84952501983567363375, 0.63412405532441706857, -0.58498638934369362463, -0.12720178496631617815, 0.21784823048977508542, 0.71785313033006459271, 0.69398488965166227338, 0.22843215428123697652, -0.24661699415875149022, 0.63324764415332412959, 1.16666455731365914517, 0.37103271662991066382, 1.62854185534315965000, 0.97837761064670292210, 0.53433772871772389212, 1.30298468587362359372, 0.14984775239147868353, 0.16680046538045983917, 0.04624082996253969791, -0.09838926588846697019, 0.67423353174909239627, 0.44833469547295246027, 0.94870278554381082259, 0.09011477412278590116, 0.35872783269724967115, 0.74597927445065370655, 0.88906412208582941137, 1.20223147682499797995, 0.83889857334782325093, 0.19639857280995798305, 0.29633108661910012582, 0.13394872813537017642, 0.89930537007226507562, 0.28223772771017485717, 0.06507541202294986338, 0.12513048146999022903, -0.51420080474851326269, 0.82669183979348526137, 0.18407706116731195611, 0.76683410423421682900, 0.27236159250375457930, 0.59352672470201062183, 0.12175177304518453036, 0.49682314357094914392, 0.31933862959148190397, -0.15814531699753264782, 0.82633775945555643094, 0.46511523815113831049, 0.08511086890516156300, -0.38175802102994244036, 0.09903809495245480266, 0.67708302151144195147, 1.06541520173828807572, 0.23364913546208304629, -0.24003830930518754716, 0.54917244085180938473, 0.46317942795325894867, 0.62779123745540765800, 0.03865105035465379868, 0.25848677198291847290, 0.00155442282629963957, -0.65499230361440929915, 0.89744372030487995495, 0.13647508950874137623, 0.41221601238139271572, 0.27702923652431316048, -0.35816066673235491535, 0.75864850069989042680, 0.67907857540806526586, 0.28038372471750511172, 0.08699457210465372237, 0.11588773954575359859, -0.18435272959900173007, -0.23813710840376914324, 0.40616067166699021396, 0.71812990191932279949, 0.87202171480210743937, -0.00394196304526492347, 0.45949421208669183336, 0.11399441515224424670, 0.01578544977855123044, -0.76977634304948838739, 0.59493053657595895700, 1.05804457408179142242, -0.10965460345403242604, 0.08914082612150435458, -0.09201204880445953971, -0.15708021790070347823, -0.25149571930419306609, -1.43216396822243230424, -0.54739332001712270870, 0.01703196914610338383, 0.51339111362239320258, 0.43673143448093820762, 0.98468695364015501603, -0.51921001504189967513, -0.22850151833314996663, -0.26664513715991156051, -0.32553967882401441125, 0.83671086338679157013, 0.51563421542179765922, 0.29816415795593559368, 0.50117053632075792002, 0.84962569460146808442, -0.03267741466316731458, -0.39945376151391703834, 0.07388418970518892404, -0.87598255738672736737, 0.19340106816875124451, 0.20517324112231416500, -0.09085207951360414758, -0.04427508635881688415, -1.33341759575567975205, 0.83102654649241824991, -0.18790478792572853917, -0.15770166589326092832, -0.45054830173095539170, 0.18488056411850076199, 0.08322096258840833150, -0.42664303977515938282, -0.53787289887621847484, -0.19051767632382013429, -0.49444347588070913790, 0.27849542027323925808, -0.46507611317334673906, -0.78022820949513504996, -0.78648474213990393622, -0.53315855927644606460, -0.58105499980969166351, -0.17333224228235188935, 0.51222466108513375893, -0.57659240367564823515, -0.11984300847394518952, -0.04788045976587446129, -0.20051220226401705871, -0.01083274783704307742, -0.31255905382773557788, -0.99381352665865285712, -0.31084569334385603323, -1.27498316092423547019, 0.10297921465603160351, -0.55042974238424013933, 0.60773084135225385882, 0.50904106416120264988, 0.13646592906296600711, -1.21188659797317210831, -0.24886077549182333835, -0.12449904954125545764, -0.18584442292001071562, -0.83518412859788238034, -1.14853672897300196354, -0.63928607167665618682, -0.29133646858613371222, 0.04412143231880161220, -0.28308176206361435057, -0.96521488844779201877, -1.17540718283114764553, -0.84594334961251183014, 0.01338573217506372481, -1.02404735143833680588, -0.16557617856716433380, -0.63615437043058176148, 0.55070920451357818770, -0.38258787859814979981, -0.65367739893604626111, -0.57694885957002395305, -0.52716767464042402214, -0.36928737910911046960, -0.88984956730060593699, -0.68897822848007184149, -0.38993291082631675870, -0.13056741986821612800, -0.45585873287463196668, -0.36360557668993753744, -0.44754929370200391414, -0.80088641268058213818, -0.95025630431723484559, -0.06416838811955027921, -0.83735830334533134511, -0.41267254068397934974, 0.05041642421019609799, -0.21498168180080301504, -0.75025452531453495908, -1.43426249868931687814, -1.04688160199016833118, -0.41544211185661900076, -0.51859832862969068579, -0.24453283056657942884, -0.83145529444733456970, 0.05613704187140888813, -0.27058269219285879803, -0.66052516057898391644, -0.04967363310500094720, -0.33263230602671656211, -0.66402953361687944156, -1.27254415735487036443, 0.01893368184837762591, -0.92869422146093483228, -0.66732815041430093572, -0.33965456772052349255, -1.48758620046650724689, -0.04424553583197587958, -0.99397117822985392444, 0.59147907639285945969, -1.12643041123386766778, -0.73673691699023269308, 0.00375678375478749604, -1.09348318625038043450, -0.69539002553094930725, -0.24365605229906012186, -1.21367244273211460914, -0.49283693071272016706, -0.38284092767356758813, -0.50684889913463460065, -1.03979809095839992317, -0.19289905896394449192, -1.23591596626342292709, -0.09992896929193662015, -0.14470189404088135143, -0.64575705346167300291, -0.61485128052609239191, -0.63013209661839864051, -0.95266195469567915843, -0.63122134787105499409, -1.01105848706594603215, 0.09142568942557105505, -0.79557763252826063649, -1.42128572962394916779, -0.04969528838534675863, -0.66970398980508527575, -0.66346949878505201426, 0.42012805576946909980, -0.59236011640340546780, -1.01640648480558359879, -0.49855586069068164035, -0.45112471462941894362, 0.68481331647284937603, -1.15426191896366381329, -0.43903366705538526826, -0.70953963627570171280, -0.19234278438059781990, 0.05621003079617981069, -0.12639883920454220156, -0.64932571362080160338, 0.18127118652588747327, -0.93261186143519003711, -0.87452256849662068916, -0.27909031344600609970, -0.71355120992241216626, 0.18301867544720207270, -0.30318289804925618869, -0.89354496774281710891, -0.62062692027051213906, -0.23997589466504215538, 0.45529132361003654683, -1.28380248176209565969, -1.35630225180059293955, 0.19373940774267606191, -0.61701227638667655029, -0.22996907981293590595, 0.05634378817569174469, -0.42194633953715060537, -0.94948169559356454528, -0.07672660933748132184, 0.17694830393342064756, -0.69862722856941850136, 0.05720548092465838064, -0.80633341027926463962, -0.54886498837083474989, -0.38808228952331946804, -0.74949394631625421148, -0.92297612818782914790, -0.14140335901965322973, -0.49718279551560035090, -0.16903465149972229931, -0.46842226074622544951, -0.52317254041778182838, 0.11592033640718296672, -0.56015153876511480924, -0.34266840878899940126, -0.63566490022022070772, -0.72932252304392997111, -0.66433874166995543487, 0.17360289036878884250, -0.67486392124364169565, -0.87695946803756941179, -0.09249844471771012655, 0.81491004054074300811, -0.57092445801769753366, -0.61654172308179244766, -0.64463604493765325198, -0.78205312771126989091, 0.05979578029258814098, -0.75704473577607434009, -0.11453836023367375274, -0.23411117231350331291, 0.81753015221661273770, -0.24400262921861648469, -0.39544195615696625667, -0.18523537039471940635, 0.07209007023555058513, -0.28480757194405953436, -0.23178555909489118037, -0.53173334352921508561, -0.28882518748441543543, 0.36566886681460769593, -0.49086620968944177923, -0.00005392053749875281, -1.57820763416456855133, -0.38947989234944668802, -0.77623664625153510066, 0.55335154523015683825, 0.15081153974259831063, -0.40393068891410172094, -0.24297661283977109448, -0.42435551405553084692, -0.76159519721459278951, -0.06557676273038465231, -0.91619915326210832340, 0.01131345331409622235, 0.15271380104717968695, 0.97208703428564247062, -0.47661547820231053851, 0.10496816928053179230, -0.55150210444442260282, -1.22540454332020942729, 0.09385423092486136487, 0.82136067668053747148, -0.21724391924306540869, -0.32919750351170340430, 0.32626385761997278578, -0.37614009163759382082, -0.13362758078851746935, 0.46769360868061649050, -0.00334504022973797221, 0.03603765742757472634, -0.20414388585299902967, 0.06446169025841284872, -0.71915608907066441713, 0.19580459991832444011, -0.32740313679898697119, 0.73762866233572765839, -0.14450339193391525949, -0.32662181933416767343, -0.44846920491641539819, -0.05760980789281087283, -0.25819903448095510301, -0.53911420270669396793, -0.54523032791233505545, 0.21185377552948567170, 0.02693456397615351383, -0.36877834599828718254, 0.09387714561014448567, -0.16700559220905045188, -0.82082847777071488160, 0.91161858323264743920, 0.26360911581053370067, -0.31687265675514292296, -0.07458261330753396257, -0.05694116819056579959, 0.25798506608792387551, 0.36791293538244335126, -0.47813373792787561278, 0.09639519907250117259, -0.03388707122239711933, 0.25218467910650355090, 0.68033995976983274723, 0.41675448146791416182, 0.62619243517912470676, 0.71441328522392488765, 1.15343297855284609987, 0.51534724884067351081, 0.65186615884426846179, 0.39007046169817050085, -0.06317371856025907895, -0.46191422309503837873, 0.43504044744580988580, 0.22758164444004441718, 1.54778754525527784125, -0.23674366908448696289, -0.37488542283948234068, 0.48636387138868264479, -0.78884965219652825041, 0.01745452624311394363, 0.75381168753276595051, 0.51654682418928243859, -0.39379316948140130616, 0.60869783863220405795, 0.15243255890833487531, -0.15973731870056717685, 0.63072755426084148311, 0.05212994331585985019, -0.15011668450727871926, 0.16416765869989008442, 1.17901053800343280642, 1.12589470516057543747, 0.63094159885586775793, 0.30481147688167636867, 0.49076857975218179941, 0.78079546553509315832, 0.15539206977474379068, 0.07956815918981136049, -0.30710109432086807324, 0.63813219911271057416, 0.43687259765505970233, 0.78615195161894302345, 0.19315030781542022464, 0.33740941824875692046, -0.12865482487067608774, 0.00442710778412236383, 0.28929661359784769603, 1.02394159728092426498, -0.25958086663003465677, 0.68937707268967118868, 0.87191353582148689583, 0.58923372082643676872, -0.06724726667668345925, 0.23882627459495850419, 1.12977645872468102084, 0.44963721254531180405, 1.11980987103061657706, 0.52675639641114546663, 0.22627820377924751094, 0.04293899722825050835, 1.43270952852694910540, 0.23652037677530962645, 1.00709198143278477744, 0.11287430252886815341, 1.05651573523821284972, 0.83811952241456955104, -0.01216514098794108101, 0.14863058309102944454, 0.41293676473826912421, 0.21082991970806763682, 0.00177248042128508310, 0.48118503680154944835, 0.31367076040107544355, 0.21437085666620975255, 0.65192692092212600752, 0.43189686319640951950, 1.05468932048363273069, 0.56888317313309588741, 0.47888702159550000648, 0.53117827224122837748, 0.32799008857510569825, 1.17875010604274166326, 1.35174272181644550272, 0.54946278631913414436, -0.43225164704733021193, 0.80296906233219056404, 0.58758881795619744004, -0.32930681389262472791, 0.55338462738061322543, 1.03929362604454822439, 0.78538219877869586139, 0.96194333870300319234, 1.00377586167916343918, 0.67206868376318074088, 0.60575976019857058485, 0.05233043044891738660, 1.52281539724171288697, 0.63813194771949566242, 0.62460092294082258935, 0.96373069641613717806, 0.79260827151432267090, 1.05239242151947109960, -0.08767352212269741152, -0.16574057069494985139, 0.58893956654669621376, 1.51676214052649394048, 0.59537552789589243130, 1.03536927142030532600, 0.80327416334400147946, 0.94876679282516218628, 0.43151584811340426562, 0.61160070184242421032, 0.07608322915274556841, 1.09259809712105759871, 0.92460090201728906134, 1.65230351971014699508, -0.72930051332118706053, 0.72709370777205073466, 0.09939527441212492320, -0.16489324775952651780, 0.92778251212549633919, 1.27175623100549639588, 0.41078472156707662721, 1.19693753197186270043, 1.02324879248183964187, 0.61789989431107472662, 0.26564976973385567849, -0.12797984661437233900, 0.52707792625643568041, 1.05940871078140164130, 1.67277598165879037850, 1.03515750845628407006, 0.68732874440685720607, 0.74513630626716409466, 0.94783290615158055648, 1.26631984815221310114, 0.53888852383233543097, 0.67694217874555806524, 0.78340787151002322375, 0.56987404526379847880, 0.28199503965131522687, 0.70235276791920497885, 0.06840349690184094333, 0.40338357371530042528, 0.53103317875592404018, -0.52387842616838042886, 1.21285695827774420152, 0.57616131826865457644, 0.27284882105920060802, 0.29196557915481535783, 0.41099233769716542186, 0.53915102779018020396, 0.50372338475264266666, 1.21173061912820623220, 0.92773331480576626262, 0.49105493067709232724, 0.74892153167248420864, 0.15200520000596112746, -0.33296622370108897027, -0.68553740439086707781, 0.77214091866423695265, 0.06024273470930313623, 0.14816911102485530449, 0.56528697167416064318, 0.26469788875773403447, 0.89318627022512786517, 0.88391783387060174348, -0.66915565429000367637, -0.91834041731137716624, 0.52075821137667110960, 0.30294920471847114340, 0.45493739783697578760, 0.03779220149536999251, 0.33266528079369717119, -0.06351926868814211646, -0.08312714585297403813, -0.11834462868010992009, -0.29368151255126423438, 1.01510368885513857506, -0.03452246081878396211, 0.86044105743816190479, 0.27732296004555873070, -0.19446164106935587279, 0.22561016554881585527, 1.42976297528745743826, 0.06365633955873195582, -0.26838676159044044800, -0.32425551479796582699, 0.06921796254123513403, -0.02537958498860187762, 0.85873030180515241838, 0.23142192336660827312, -0.15426130014432859472, -0.30023716257286248155, 0.20229094109573447779, 0.55877864279599209762, 0.29733490080009172818, 0.09799010059426177532, 0.20178379093235315755, -0.30143384990358867981, 0.61408914196603769931, 0.50511285167563191312, -0.46589716206946396593, 0.21274684825404460708, 0.09357839696721299838, 0.00367975678743007184, -0.59658358345794793021, 0.67457171370778146180, 0.32454154192345485708, 0.02164938185763609635, 0.11588512721007053485, -0.17860133355285667811, 0.11603113635092385247, -0.23664952898848332086, 0.24245022548086797576, 0.17225821314736428080, -0.03083987930021153678, -0.46370655799402515918, -0.34462336515864205877, 0.08015112746309818736, 0.48261220740902038839, -0.21215136197250142325, -0.20151246998271574173, -0.57224419883050647861, -0.13652571694367793276, -0.35310904952512056632, 0.31866194143162890340, 0.18037320963150493824, -0.46678188589944363818, -0.55957250782901346575, 0.06351989780122636875, -0.74950997742283576031, 0.05244553813869212711, 0.17306886663898246592, -0.19648616676385091684, 0.40576099131443688073, 0.35228846165031219018, -0.37924914411327326214, -0.56402713311905250393, -0.39780035458782470048, 0.35779400951339396242, -0.98104390186993539746, -0.04470034060478821070, 0.48168785902705935076, 1.16024637958765608481, -0.06206016338010470618, -0.80143145839080121018, 0.17028026911071397764, -0.15324748300046903138, -0.05521730106949093719, -0.20223505664319940678, 0.43182178364023215966, -0.46208399480219020106, 0.31069540295327513491, -0.16930378276956811745, -0.79004324925507996014, -0.96749386971590101147, 0.06610516771393903923, -0.28072618436283119925, 0.02402835131838354510, 0.39480545121644583517, -0.01872390408430696374, 0.19632699134204664726, -1.03262192889126414030, -0.12465954551508426351, -0.99044616704089682990, -0.42701464150207957271, -0.14242428402612061777, 0.42483243166512357769, 0.33076669633611416321, -0.53593227927545461498, -0.24334053320685608557, -0.42122182755724857373, -0.88675345892659052094, 0.83486593399852471187, -0.39822145327019525807, -0.72816984685331398452, -0.23254231536472164121, 0.31119489080373036183, -0.58198609816608981582, -0.26601844356328996355, -0.27430348357975192775, -0.04447301028532368861, -0.24964412152814469126, -0.13989758808180358107, -0.67990196296811755694, -0.54765500996401250688, -0.40357026597836431758, -1.30666353833579385046, -0.18156827021237001429, -0.46276894010405267288, -0.27303329226543693320, 0.06396538149128461193, 0.35284805367999133585, -0.86294987206660966894, -0.86068836639804369160, -0.70405876375652565180, 0.01589838635976426051, -0.60919814196034616227, -0.42187388547173676479, -0.88045131998471204415, -0.07582199193283312155, -0.14544859018034345111, -0.88990690764561142245, -0.36469225865207899329, -0.73571233513153622141, 0.20630655673036951692, -0.99934928381158316668, -0.44058107056843370808, -0.98337957671734677056, -0.38483104199185091954, 0.12413424277674844909, 0.42548009137919340361, -0.85629570484628447780, -0.54882464143893339159, -0.05577762292327337335, -0.67429430203769902263, 0.10806300376176136702, -0.84223424424139481381, -0.23531736205487668867, 0.05724240706712346860, -0.23722108080858195223, -0.09891437119118606791, -0.46004898888524881073, -0.79994224599148333610, -0.46161880704342272974, -0.23036219316614364727, -0.56435692424298866676, -0.05722261533142924961, -0.82277339516643266393, -0.75772497051321530126, -0.35491206084643467378, -0.89466177591600848373, -0.99021936112934927365, -0.64861274135492297255, -1.38494776572829692007, -0.26903546235157288491, -0.85908911088749817520, -0.81234954125447922380, -1.47714709751061046283, -0.65981507657065963102, -0.21136858047909989899, -0.76773967487306948865, -0.33843209561061082802, -0.93884242560177755266, -0.01763482333652288681, -0.36627509261754209735, -0.60684054560355971120, -0.82050794737775234466, -1.17289098050105922155, -0.59265286314611398399, -0.35172304828475370320, -0.53430451440528081708, -0.55815408217969608717, -0.40084861704557167572, -1.08410803301279301536, -0.02491021888747546598, -0.68285622422863268000, 0.21111589882712689104, -0.06544098431480449563, -1.35386211766877173623, -1.27833584097058206019, -0.61526781285623100004, -0.45200351230942853453, -0.13741448363341957073, -0.91356371675821357314, 0.08126051002060963313, -0.59716018877096943562, -0.70046551338838591860, -0.55486136140082731316, -1.04788947672598253824, -1.25525670854814408450, 0.26337704673977202496, 0.13545685945677299333, -0.32312583700570340906, -0.90170006432019689235, -0.27323573751916685470, -0.58940463960217370776, 0.12367218407082625209, -0.04017161262408475153, -0.37200002209172167156, 0.06207796011242894263, -0.47681498048264608913, -1.66286172317415026356, -1.30984578446686272812, -0.33483175901285977538, -0.02996586221722852272, -0.24720360639910171630, -0.28001137073350113038, 0.14599763132652210373, -0.46881335996769474272, -0.95029569144015701987, -0.83585697446965534496, 0.07179894130804775898, -0.49040708682098110849, -0.77401468043835730537, 0.01232646477994281398, -0.74066586232626185904, -1.01389625738282385115, -0.85120241972426380350, -0.44465705640694774825, 0.29350325799335708066, -0.96657462028117802078, 0.05923209361887715740, -0.79813081785773110077, -0.07576081045070393039, -0.64047786153656605102, -1.00919760336455643923, 0.54335947315022337101, -0.72417268296155179463, 0.34957848448830625143, -0.18600828784791595405, 0.16078401772625161570, 0.02479808865800497975, -0.22537335688654505650, -0.68573946108266470301, -1.17804071341415594887, -0.60226235775474290524, -0.06572245992584452301, -1.08588868214855693850, -0.64327248614103982316, -0.88492890159843073938, 0.09918660282685076712, -0.11747090715318980747, -0.21389331460586602507, -0.71329622115684143679, -0.41039702957492429913, -0.87311287726185360381, -0.30378474561924617703, -1.39939945250303598989, -0.18373379840477910285, -0.89967466105524529496, 0.74219782552596269554, -0.21109744608409386490, -0.67228733370689330862, -1.26668965515244535958, -0.18782845050874066861, -0.00604169236120988273, -1.00194374937913943668, -0.49511412877467836369, -0.58106972033857118287, -0.36155572575240457756, -0.51728983930371696953, -0.13936528541024473249, 0.51703716326666504521, -0.46599052428410880911, -0.31556988455345713618, -0.18843028247567225608, -0.95827931614665218252, -0.77939870638649566548, 0.41763350519060615795, 0.66771019539404941057, 0.50744138608564370863, 0.17405255412243067492, -1.58605709145354412115, 0.05677222937558284999, -0.45264221007980309786, -0.91994303636133234470, 0.05552787765234723505, -0.39547070392521277427, 0.16964199310590141745, -0.62645256259737769344, 0.58897271987692778605, -0.72357854340581839736, -0.61834641661313949346, -0.11610408628794915975, 0.73334983919828877763, -0.82206540384703419289, 0.08577714010697949643, -0.17923585085644522241, -0.76219865221422489210, -0.70994568675983282802, -0.67447982374085135859, 0.73929284432229480650, 0.60342658011423189190, -0.45180160036070809992, -0.85129161488759463872, -1.32259471689935614869, 0.11859399855184973860, -0.02089278914344876864, 0.14277795739041868051, -0.02173774417837680620, 0.14426546921739233365, -0.56588056345395920665, -0.80958550931292561081, -0.27628256433479669862, -0.29295471864413097363, -0.16389099540279433720, -0.37520705621996097712, 0.71457390961423972175, 0.82634090016487871111, 0.32761767196845975603, -0.21499999840207878465, -0.26257139762404402283, 0.23426422274665253953, 0.13672184264078030003, -0.03209761977437040892, -0.07605152808646388485, -0.07379717274291772156, 0.11372695349531633524, 0.22478933588796723431, -0.78701789045846348714, 0.09944376694902126723, 0.70346865489446375186, 0.32335604128254991085, -0.13542390924745581482, -0.16255261241221652391, 0.41022521787213356292, -0.03266574823573484276, 0.13681608970298489436, 0.19502939753570855408, 0.58983712271359101109, 0.72705556263018311647, 0.80491365491102362384, -1.19586810442335989002, -0.55802528131394013577, 0.55779489265010828714, 0.31386097140680302431, 0.18989281476863267839, 0.49194537549643146956, 0.30552209144061404089, 0.51557947118158242716, 0.53221110542138472255, 0.49148375057772808461, 1.19985371683329411496, 0.80704492322327436860, 0.68557895586179051772, 0.62339900205564324764, 0.96936492016729502996, 0.37478618230468346839, 0.27730685631953033710, 0.52824305847309005468, 0.63398077648294948183, -0.06975744468697103740, -0.07112319941317607475, 0.99209244031617971338, 0.78697712695564647767, 0.25379108688831203411, 1.07503068065064155689, 0.04840990587987314964, 0.53697581930603421885, 0.36249361050276662777, -0.47800762769314025791, 0.84951584628797427445, 0.56080050040065310135, -0.21859051583024463605, 0.13345552319488257487, -0.72520409682586484124, 0.28953788893780008085, 0.86801140485305805505, 0.58166764647593927062, 0.06746492638892270799, 0.01593455592026238321, -0.03269514915520094211, 0.26775664727400655396, 0.06279817814172505841, 1.15606024080976332868, 0.05681302049138470256, -0.07196484666444802336, 0.26872285182630573086, 0.53244854624684034849, 0.93588603715477514111, 0.25166506345646305620, -0.29162624455293106873, 0.06818384236205943250, -0.00272854930977761700, 0.46537290275089737701, 0.47212003751861153500, 0.49164629663764730649, 0.18387303544697403135, 0.63733262408682667122, 0.55461410025882718067, 0.67210480214495227358, 0.66490317926310060770, 0.73434057168857713727, 0.40663718042169072486, 1.08877200326880041459, 0.23434856632368405993, 0.43946741605170075440, 0.35292689824542744503, 0.32923726417890297746, -0.23515582219854347379, 0.90606960517228207763, 0.68598919186582696383, 0.00365806727584827573, 0.01470479797291768254, 0.40203257479459453272, 0.08534061071748655358, 0.61277037456890237621, 1.31853156988430564311, 0.54475354626843475181, -0.06928222945031847768, 0.20988003982163960792, 0.26746795139832735266, 0.28852699985506580216, 0.28447968570803999766, 1.26997003894694726611, 0.54205340688356618006, 1.85728859597432571782, 0.01898592622296002563, 0.65763161812251402782, 0.68749799562738789227, 0.42259403581149868057, 1.29643662764656597552, -0.11543584813996643490, 0.51835549613909193134, 0.34475086968681517563, 0.45864470117785094416, 0.86096955297726163181, 1.42678603130714187763, 0.05744702156111092251, 0.04637221617904274673, 0.35253436183915376478, 0.40608288150364479918, 0.28063690096379939609, 0.93218428259467789321, 1.18369172896719865307, 1.03638406132216953637, 0.17379746505606458173, -0.73733840841551556711, 0.77860414684674883468, 0.72503538458671656919, 1.32402517005365849556, -0.26629026298566349507, 1.00213965742861721431, 0.47679519191268304734, 1.15724060023364394567, 0.65155209759375642964, 0.49950442872958372709, 0.92013100665118230381, 0.10437259592236586281, 1.54774027368193212162, 0.89047713230443781285, 0.28679899841997585908, 0.16390695820375306146, 0.57285325931167396796, 1.99654547013740568673, 1.08524033623998250597, 0.02488887549185458958, 0.68557668399425408801, 0.50137353033476694808, 1.17526156277828652819, 1.03412184125869233142, 1.11804925458929593773, 0.22620875313159988229, 0.28365894996271168171, 1.09219452606212330537, 0.50841217653441628244, -0.39349973929880366352, 0.46126768753535501988, 1.16663617729228086262, 1.12474006325244224591, 0.61919904989592922107, 0.22383939814378583932, 0.09760166669731729083, 0.31194815760897443013, 0.52258537415636507895, 0.15297029992424188150, 0.88678458134298043802, 0.48883779182578174227, 0.60914309105903785557, 0.37557054478717671886, 0.06005654534581583714, 0.25358764803017941336, 0.34170362957894473421, 0.24711967526392902839, 0.37071340076327430335, 0.22634809413452669502, 0.19575658211183755153, 1.25554313924362537236, -0.34515820857510948194, 0.49693103559441059724, 0.85044948592555902334, 0.71204115177618243493, 0.10682179823135123931, -0.69618460646852065032, 0.60912882539465562637, 0.59357781711261903279, 1.22124745616878049859, 0.62506266661129294970, 0.44177255977818347121, -0.62598995780021171598, -0.26826640373320864708, 0.50905710598772235809, 0.41481064203395084933, 1.04316357703706263926, 0.42134826346159187649, 0.47195738394482089628, 0.26302799260034376294, 0.51873977587689357183, 0.22077329716079036048, -0.06504374783474986543, 0.42555322722269145475, -0.30860781134177406315, -0.08815278141437055126, 1.07745176042835799635, 0.72261000425525701019, -0.17857423292535945514, 1.43846565726275277264, 0.16605437753689142322, 0.55927181175946261149, 0.18952822647143729995, 0.04464138195681935373, 0.04151053377360691687, 0.10959287894374268590, 0.32882490269412106842, 0.96886710120333841001, 0.01832098542699073240, -0.00814755411176298905, 0.64634685142991754603, 0.36278580337204247019, -0.34119937236498709687, 0.35495129194854319055, -0.65207991697424050326, -1.18531684901667500043, 0.31492226848349547108, 0.45425795031708526839, 0.22817654608489987278, 0.25182819524949207057, 0.48920134210900356742, -0.18234598902200260806, -0.23897952279156758904, 0.29174653520277871177, 0.48122199878913018978, -0.14640404431699347021, 0.60523177906932013048, -0.70898458226282057293, 0.27265410077866636396, 0.07123778748391920712, -0.46578955734387994525, 0.36633011434316425925, 0.55855940125443592770, 0.16681595918602168394, -1.52000553903962765112, -0.39044640428942978261, 0.17021265148387668131, 0.03892543510151335456, 0.07430784959314171156, 0.23218778881412663329, -0.46403160506127982821, 0.20929156064001580573, -1.47524129457931296727, 0.17907299169103785275, -0.49382241764693052266, 0.45230827072826323976, -0.94563447267385558614, 0.27652153485205155370, -0.28175574991082641407, -0.42598991759978188654, -0.63084436216297190558, 0.20154165498293924452, -0.40954123276889259353, 1.77923899735045365489, -0.73786174873831056331, -0.89646552322357031528, -0.12341118253516036862, 0.71868535615028994457, 0.10232478166910724537, 0.44671051198005473637, -0.74831011341973685536, 0.42898768723478503118, -0.68399924227535202625, 0.37654275500182565750, -0.20454732202400333074, -0.20716655816744486418, -0.45091772187347589629, -0.23466745599461560912, -0.83313812856590518940, 0.31559302878918005231, -0.97293766260215830410, 0.54253407937581110687, -0.21266159155136193926, 0.13481519477391040729, -0.66247053699990288145, 0.01727171911595837273, -0.74909467440450161480, -0.37457717915031152334, 0.92612961155198170182, 0.61333911516683370380, -0.43261277411299092766, -0.77964489022463978607, -0.73465238613458772043, -0.20847933032854223945, -0.11327795300548998414, -0.58658206993013328745, -0.96637592132685001811, -0.27737273552049868108, 0.26177343128438840258, -0.16784850526392680603, -0.25222571373984559173, -0.30061943954212622110, -0.40597126771418146074, -0.61479553918946672209, -0.13675895785544028849, -0.78512996781812649516, -0.24072142787082964621, 0.36055050323009374402, -0.26279899493461311710, 0.41322987037643643671, -0.10987922011180067949, -0.00681339201162295405, -0.70026548457276005522, -0.73507594892703864708, 0.17402330747967248437, 0.12615587395244115543, -0.37941480256890353084, -0.50058521965964963840, -0.03365095980561666478, -0.26345118893281460615, -0.38631634512506446422, -0.22545149826384319747, -0.67077215711617921290, -0.46879536593309151238, -1.05807056782024577757, -0.67742492436260626221, -1.00671440465981998358, -0.49524124548469938834, -0.98897191981327803312, -0.42253517095591131003, -0.23267582027507305309, -0.71255762071027162197, -1.05852820176401429819, -0.94175250938865451289, -0.90371214491212792375, -1.22332640971659989404, -0.43801119267503219046, 0.21822773954472840519, -0.11029014805428072332, -0.34343242695358833227, -0.38023291000808401296, -0.69854404884983556379, 0.75790030725799317768, -0.54625411125406542201, -0.65857373214094461211, 0.00637134131047079055, -1.12926753369536569949, -1.07447960361990935141, -0.64005225781011187980, -0.29118701869448376796, -0.12575747098560680071, -0.42587631131466863188, 0.07698924484405256230, -1.15178360535631796324, -0.45058563250871119799, -0.63447819757467827095, -1.01280649926530186278, -0.26049067593033847778, -0.15470450759387116557, -0.74191788113963552664, -1.15716075231603454654, -0.47932536540257753011, -0.14913606404157825347, 0.21214238372128646493, -1.18276156625526862598, -0.24958308908082593724, -1.90530241728302729420, 0.10679560138779220502, -0.31344818779045929835, -0.29894251017750722577, -1.19288488198313746835, 0.06141631502468536397, 0.42264826370647567977, -0.23519007556942411741, 0.15219420075530054604, -0.90413035711960132446, -0.40499911499573798546, -0.00356459229460981408, -0.92021342492310598260, -1.10162917797203263603, -0.83320273940282951752, -0.45245777981558499281, -1.23386492924998458776, -0.42641850057823660336, -0.53422691726549276225, -0.50601026157548933959, -0.48612041145777007412, -0.61846119464831683654, -0.99856082050144334694, -0.29203148395589861863, 0.64338465528490429435, -1.32045899264625621328, -0.12192721071727768978, -0.26184933231252704999, -0.36072488238407779404, -0.02661506079833453331, -0.39288669370494261335, 0.28833514178362318336, 0.14651656669761770058, -1.68410925397477395649, -0.57786326105753693660, -0.72362199687849670937, -0.27653554950571113125, -0.86441446582356329387, 0.24537917406640297191, -0.51907580245778450756, 0.20816612233608422500, 0.03145792081784837269, -1.15173693688325839979, 0.17545364183302136762, -0.50614108836963123128, -0.66529940741243676072, -0.69594072423293662499, -0.27824455958251098631, 0.16275935075232272897, -0.14831716516713305820, -0.70576177700029063544, -0.22414038858309720537, -0.44205801902524510805, -0.73690865202858457650, -0.70397878755875731294, 0.61803287969459219653, -1.06073561312011666047, -1.39663166731524812647, -0.27370680988549161983, -0.84548435136284505464, -0.84740391550912952390, -0.26309489938117824881, -1.08426015436129019598, 0.15281799727881401063, -0.12934857160605073290, -1.29883411018401639936, -1.09484644562065192019, -0.27635169767334599733, -0.31239665649660774971, -0.51165436180935164323, -0.35468191762027739822, -0.60486936029773585854, -1.05557480789569413382, -0.13185677412788016083, 0.22612465508451823348, -0.57628622041137500531, 0.01695254757415892710, -0.49133795640918298542, 0.36393017363707325096, 0.18412235538378324273, -0.02529587391448340217, -0.91191442591036708798, -1.27833200294453708246, -0.33421098740212312750, 0.63762308769580422485, -0.67150259284760616119, 0.78042424036257496134, -0.46726246976247409748, 0.06209495275158732897, -0.49392788777268425982, 0.13705728649682108466, -0.45035111747297418283, -0.18689937394311861851, 0.08194575631634382074, -0.48101893291348557513, 0.04826437015835710609, -0.32522908126551247632, 0.13032747786515080923, -0.67178070499891417988, -0.18097758827078602728, 0.64892404967088479495, 0.41754301729200560267, -0.55107611374043630192, 0.53318516608261568024, -0.14764429300678472146, -0.26839410614066072647, -0.98134125606270750097, 0.08071336738302867242, 0.43001431840315856858, -0.82131202463919805723, -0.32141528912388772632, 0.43129499999519133047, -0.03001579594789419159, 0.02854397618398535341, 0.50881118717811324448, -0.12832441295878990739, -0.78854604707295872235, 0.77029746022352463264, 0.46810216573196267165, -0.05910862388816669893, -0.28987537612510777540, -1.12901742235124302738, -0.19150354063854524433, -0.28387599014656400565, -0.38510425730853764525, 0.19869697973667252433, -0.44840222884262936898, -0.09234883320199208101, -0.26312026154411227719, -0.35699985373010401091, 0.30407277316972614045, 0.62408471113819607989, 0.13587361554797333651, 0.34712785352799280236, -0.98575554991770186763, 0.34792912424419691897, -0.34843543126941417531, 0.03792673012872253141, 0.15639608035029844979, 0.50637349454278979266, 0.16839668335321272830, -0.75731324992434712051, 0.69307650933293640794, -0.06503648042868283297, 0.44952084649417389794, 0.36867159250774161894, 0.75269735417193606164, 0.28029954475163287864, -0.48957081945540681378, -0.34338193331512856243, 0.17140751494491798801, -0.23910087082515771306, -0.29674667688894745821, -0.68996875722852846202, -0.82667167118672180148, -0.08910339402552103216, 0.10745279330419771346, 0.81315770088113514813, -0.61352326964070891258, 0.70247340265876245802, 0.63893222575411301456, 0.44616167929429045858, -0.77152609834227792174, -0.06678848492282668525, 0.27321350529238075389, -0.30931329805468621430, 0.47812040216205953236, -0.26840019295708061886, 0.29593334159151041263, 0.28385819225852532144, -0.49794994732546021821, 0.93462094745566870468, 0.63869817152511798586, 0.33738833168861431799, 0.02260308381647624376, 0.30773794317354485894, 0.99492813936674373565, 1.31193743937484397222, 0.40197335947824541780, 0.62910012554327954248, 1.03348535588596268653, 0.51481301653649458316, 0.30092055364254582939, 0.17540721724754382294, 0.03156933275216089463, -0.55959497760402820976, -0.20704083738011419547, -0.33066216717563245986, 0.04639160010576809379, 0.22937026487934050745, 1.53715999495540689423, 0.44735849327560150979, 0.16635290292955390301, 0.60602948773428466644, 0.94525353712619220037, 0.52139392427825548282, 1.05972500108804035257, -0.25580820655816216558, 0.71259326078180729169, -0.06858125812463805593, 0.35657213892722500193, 1.09451373097149140534, 0.42448438120697168374, -0.32464117389470953379, 1.56327198803940214944, 0.98590261426182923543, 1.12038894774103336971, -0.15692115468091627495, 0.02378725583988672287, -0.22744744279582485014, 1.33421505474147217285, 0.13868294040009876422, 0.49180184313610841862, 0.43861674628883762228, 1.43409301808806066703, -0.06889451588493789025, 0.65120035261441155949, 0.19080573401148326340, 1.38003508777166716825, 0.98910630750781858289, -0.24561193638777700787, 0.00363215441380004833, 1.27600204860118537908, 0.85665604799737027353, 0.44436796480007045851, 0.03863071089793057578, -0.26519511349846203174, 1.32961482271779196651, 0.33771534585442442022, 1.25642963337897128895, 0.55560430167454100125, 0.97352072949809342894, -0.20113838769142433627, 1.18202683462076429954, 0.36466104852098069067, 1.06806019564706211611, 0.86304209706506807009, 0.78872156473820820288, 0.36196167830117698117, -0.22173728699621630955, 0.72072405360808566854, 0.74708994935772055079, 1.03946094938894706416, -0.09782991918129646525, 1.13219012165110299861, 1.28806436913297317481, 1.41492165311369411107, 0.06783874107064280512, 1.45888121769918477355, 1.27345582326829931397, 0.45252157876384802027, 0.77859742339569759650, 0.29384417036840959625, 0.74483509775713563350, 0.20077119030491541452, 1.76029362610267003930, 0.93966216988834694313, 0.79426121463777188403, 0.70676776880864988772, 1.21640384009571844715, 0.17424906728732186822, 1.29326660399277604263, 0.78304686546440827044, -0.14867208671659515318, 0.08555729370825138069, 1.36126587212312255559, -0.00316187009500834559, -0.54383202308016642679, 0.23471251090906947168, 0.23296372808973042634, 0.59587405012825256367, 1.32492626070440699948, 0.66138105311899775263, 0.63861716267046131357, 1.31716733261947727129, 0.50295301940027370424, 0.29082322140506117192, 0.66938683125218245706, 0.94437746736750671772, -0.15520835392291598698, 0.35579131361665705668, 0.88964660623929647798, 0.57369431068737775092, 0.59105202496507480703, 0.48429897913381253494, 0.95803116150454525890, 0.72356435799539642328, 0.35155506947380549265, -0.05455611196564291721, 0.37398175488040441561, 1.49012343064488428368, 0.52489070221840983255, -0.85265668481053935590, -0.43288666997786950930, 0.40950489751272350913, 0.94575726777147006707, 0.68714779095071309989, -0.06206205821115473587, 0.51631927377915476374, 0.73501983356752165211, 0.74510452132927018631, 0.22668043754836159076, 0.67010199031469763753, 1.35141750692932172129, 0.67962560891057799406, -0.44851399592645924130, 1.37528090954953730041, 1.18640442122932721070, 0.71446164859046357165, 1.08430868365660248998, 0.94453992956372001544, -0.73207130518869378832, 0.56170993577163885746, 0.56044211263239862575, 0.20751314285535291115, 0.86492704676057630131, -0.07747345867850480294, 0.28208109388286861652, 0.47561644521171370092, 0.48886050342220588316, 0.80693490416241975272, -0.14618132468410210967, -0.38637619556587560199, 0.73065395490471551909, -0.01657510931961803102, 0.39155599968852788928, -0.04603486200300860309, 0.10656988947500761755, 0.48509031030389920813, 0.63190110427320267394, 0.28192755789667817146, -0.68154769203649778753, 0.11114186605996018153, 0.05995814907816426897, 0.14322409366302679690, -0.02576152733513409343, 0.56496743023343420909, 0.33378737011846570937, -0.69442741858960654877, 0.01554113806072821014, -0.03600821139580406838, -0.16781449651393443023, -0.20291951353975440364, -0.64671451763825071879, 0.31565307004304460348, -0.50233317215299189140, -0.91432298896817898459, -0.11198666417288088981, 0.90588853499226995591, 0.05278647644172582143, -0.40103922152371351828, 0.51606480994414150043, 0.09087243856845719781, 0.43624094656973022577, 0.22310284508305169848, -0.25933734788221751710, 0.39592791481831945966, -0.52607669626680753083, 0.36380899468465649482, 0.88504107844600166111, -0.47156909708183020191, 0.44279861092636824127, -0.01727523114722301356, 0.09372418667571799644, -0.31601977405748721273, -0.91930276942310173993, -0.21505070341731061245, 0.54739123881397777627, -1.03299302084650723721, 0.39193431387560517809, 0.05952142755937335494, -0.01325013689036592252, 0.11571353567931272344, -0.51919823248230823065, -0.37490456811051525721, -0.14790545624014020398, -0.91785845347943884054, -0.11496990475782772889, -0.47630782810362554125, -0.21152302931524169516, 0.40797824337477306411, -0.07071402223828944511, 0.76838181476217481958, 0.36474672475725983256, 0.15673536889562778018, 0.36260371161439980403, 0.18134590280800400031, -0.16926352431833907586, 0.00972666540947945968, -0.80624465275018941668, 0.12746175382212021621, 0.91955453993725722039, -0.13118585554638417801, 0.10876634549331946900, -0.06004959982388856365, -1.33122350492212659923, -0.22988592192977055917, -0.51011841540017321961, -0.57526514313792365130, -0.30761546020388830236, -0.87432840631301211864, 0.32734549570285764730, 0.07182987053723297044, -0.08850214796837158060, 0.60677608492443868560, -0.37917874394345285616, 0.40645104837625178895, -1.01457982511606648046, 0.17699292043455039369, 0.26495081331485226261, -0.45474830500997981408, 0.30774356921029277778, 0.39401741392178879497, 0.22665575726082837216, -0.28881095514636545563, 0.01126874578850395325, -0.34634168051653291265, -0.27448779644462345528, -0.71883707835340582282, -0.11338795637649146109, -0.29774610901788794992, -0.46919690286831305936, 0.17050398621143214584, -0.26379949663141910587, -0.65533280279345296204, -0.62636651764860351577, -0.12615885873377330473, -0.31064233046394701399, -0.26063777618745692655, -0.58763335960234364119, 0.38404885394865917458, -0.27214151162144950558, -1.08337036771885308895, -0.61700062481566697592, -0.96502988731402061795, -0.03818724785451776960, -0.84846894887232937599, -0.73924056851716746230, -0.64650757087000843004, 0.15443246614173777864, -0.71485611247280345104, 0.53370314648082128883, -1.08136092630748770205, -1.69631568820195477443, -0.84763733052163270365, -0.46465593106505581611, -0.43116819992876664891, -0.34297405041502354583, -0.45061415384774428095, -1.07756876606595253243, -0.73336656496419871942, -1.15837764687508171235, 0.30778023927076403332, -0.35037518042413517971, -0.88168328795324013569, -1.02455244334704986819, -1.27514210418571960481, -0.03443337265248530565, -0.50367191096677377260, -1.01201694890649251946, -0.63430381645161904025, -0.45515637415543813704, -0.27304864917678800529, 0.18082669057423939041, -0.50610760160475176672, -1.02945891627439034544, 0.14813589405009530697, -0.75273529668568861517, -0.47780492540404784219, -0.27910824595945338267, 0.01928318733085510051, -1.07590070522957992338, -0.39023194355221962892, -0.66057007982875193264, 0.45054425709556888968, 0.30305157079164146694, 0.05480606925788533701, -0.40316307562256481489, -0.98780496008975082756, 0.15246269462252060034, 0.00576137186251390876, 0.59375151925889990689, -0.01331190816625993367, -1.09783885778878120831, -0.81764773995084061298, -0.71805356390993346771, -1.09985223489712202749, -0.73192785031829843412, -0.56278654988197818199, -0.51946756868729315748, -0.07057790611149084015, -1.37399645297030126301, -1.03894657138560742027, 0.33085461719890219001, 0.31687936963048379813, -0.21162633179041706599, -0.39421960565826053013, -0.89143064804119642730, 0.22303778320110534406, -0.27415803542517858116, -0.21296627309785415294, -0.12914773632326931319, -0.08747503797385458579, -0.28238894641527567675, -1.62000266409334514606, -0.49289173404751723817, -0.16458098825443773228, -0.29714801244268618241, -0.71101459124237165188, -0.91649897656535328494, 0.28465319920509946083, -0.96579828990825400403, -0.02020711439599753856, -0.12140973286494305405, -0.18658789394418690000, -0.29096907543914796168, -0.14314503424822461186, -1.02562545507363811481, -0.53789703243979969383, -0.64172171846714365362, -0.78406389291529876573, -0.96152260991605786877, -0.44203676733266161758, -0.19309521785650601844, -0.46230167018793860700, 0.09618798274720818942, -0.40352368464418097993, -0.54456998475122264658, -0.34058418382018507131, -0.62224544448083818349, -0.55820149525782347677, -1.15049046627010298494, -1.24096053935011640945, -0.96544446712069531547, -0.31553321329062045120, 0.23987567803706766334, -0.60419523073869108032, -0.67172351588886503038, -0.44627969981765203134, 0.08375978841039932732, -0.59956577791208776063, -1.47009505778847815449, -0.16674578815027846224, -0.29548152972428110008, 0.85050977925879567287, -0.51949671865353830302, -0.29274502557404502578, -0.72084029684927664050, -0.25146401224336067948, -0.52103766716490118593, -0.71731651703491228211, -1.15304597682779785828, -0.07328300923106417786, -0.38222687800003479763, -1.55738220916802272420, -1.29687113365825723577, -0.25685309230803798908, -0.24976906113732214032, 0.52524599699670571074, -0.68904077193145840674, 0.33169421933806469971, -0.68883327401067517837, -0.34315956323754398527, 0.35908241192473827619, -0.35114631055595579845, 0.15767430115320374417, 0.10134690448812616603, -0.59311459228594110016, -0.26977257586516656396, 0.22698688421574597651, -1.06854121392182310757, -1.07660906406201184460, -0.27958002716904684082, -0.26261532792578934359, 0.06185153168157053738, 0.71192654740958416504, -1.13355180564679081101, -0.26257078981649417670, -0.85879020509925618754, -0.94363944123965826094, -0.76888685089830710595, -0.14504664402614220187, -0.44006359592568294881, -0.16527072670101980445, -0.50976390487316858291, -0.01504830019187691770, -0.11326774158152094762, 0.50919264889302273680, -0.80850968043038906607, -0.80471764676559720542, 0.21887691024726585454, -0.04657190269351341172, 0.18809697753539661447, 0.56345289134336784542, -0.04933550018533727766, 0.34677712840687979767, 0.11036034704751654378, -0.13776585253879519710, -0.24507018595981580611, -0.68233910328791957944, 0.28937792614719271000, -0.00517159549209669778, -0.07478134642911232755, -0.44495198052713980097, 0.06570206855153493120, -0.20872696801226595098, 0.60458655095861357776, 0.05655270284786981239, -0.65862575041977977630, 0.90090984728709988527, 0.15427787489946157007, -1.80047287297689573293, -0.12886353004249914700, -1.26308353323951116920, 0.31937004593161488764, 0.04784723812169407176, -0.80542301214224099049, 0.73773999097564657568, 0.59071427192057879285, 1.37240884844418831179, 0.33323351247775939710, -0.15236453252341558562, -0.03944457031550152593, 0.14678772271092499491, 0.23376272714386212015, -0.54095703708927589393, -0.30318387133252688859, 0.02300025041755614924, -0.78032856904159608735, -0.28502543319659340293, 0.87385629694986421967, 0.64291949096780398598, 0.00591414773818852227, -0.37528070873629298942, 0.52034660232980833872, 0.18175735118191455042, 0.12083395852273584592, 0.71072587550327348538, 0.51020306932156833657, -0.01815537183607940364, 1.04408022829544355758, 0.29774915964661397005, -0.07601463389088590483, -0.14237935969098397537, 0.50747499583518562716, 0.21708492673879600798, 0.29123078226692605819, 0.52076032735162869347, 0.67426440483606642218, -0.54502786700826733046, -0.02933075231832069085, 0.77319626892098458626, 0.14141384500403514357, 0.66589426457991029995, 0.35497086982868680805, 0.25112613238529285509, -0.42960577795938847956, -0.57764493074767220282, 0.10596514085456833210, 0.54907543862010954605, 0.69534332101949325455, 0.13830159065113820094, 0.48104450866014974997, 0.08041266312757563983, 0.47659038990087509990, -0.20462253886490616717, 0.91478000713491880447, 0.77942942266972981002, 1.20422466364599145550, -0.33072213481981804772, 0.08526028544147082333, 0.15247728119786596590, 0.51300812260050099312, -0.08048530599224384963, 1.46460476242846815431, 1.17253732898657592187, -0.19012198929076801202, 0.15019180477302074284, 0.80624952660461535192, -0.07919367649573333390, 0.02389169912819028019, 0.13543289998601820701, 0.15350285074466835100, 0.52082311386810054010, -0.70657208523538961309, -0.18066674538261723315, 0.41703693253451817879, -0.12297328142840130427, -0.17577663942491855709, -0.33242936555101015861, -0.06111873458378475066, 0.99094121347335650807, 0.55439311163153870421, 0.72497128885631545447, -0.08777523688642835387, 1.26468109040020237899, 1.33763304322642606614, -0.36384540955850869226, 0.50812865838944398789, 0.67307243840878117069, 0.52020308637370415994, 0.75379973344237216626, 0.65138244574071713089, 0.38831537876950855681, 0.14509770815565731228, 1.46142565812923086455, 0.53763204955200027957, 0.27878316362994087774, 0.01606418269011211652, 0.49747920179572924226, 0.44250344809259611889, 1.25065792617170701284, 0.31540406310058766159, 0.87070132846651748793, 0.16490649732728984711, 0.37342599689678690478, 0.71708990482773804498, 1.75686207827923390745, 0.54487142983130643081, 1.02704262837197912717, 0.74053836180854915039, -1.02432506878564444364, -0.22081319328007276148, -0.19816867868927989793, -0.20332182877297522161, 0.74925812452154816334, -0.41448455429322672572, 1.40627112457170566628, 1.52316307899528613490, 0.24106303095908887535, 0.72894200490218086230, -0.33145966823512351684, -0.14172849751549088104, 0.86052977729471136215, 0.61156667126855002170, 1.13727357232086800920, 0.44489004599039239096, -0.10699324513065222586, 0.56455005572604577413, 1.04175495773043813585, 1.02141098526806928248, 0.50759063342765786686, 1.37148959212523635642, 1.03319316631130120321, 0.81768211567137039086, 0.67015100844282604609, 0.72981649052830821489, 0.72665397864112091231, 0.93555919148226762694, 1.01191336425091482631, 0.21323075842938865598, 0.37251867181117248595, 0.92283095556239747026, 0.91620813462181627251, -0.27235545111370240701, 0.55885318847621090477, 0.32234580105850657583, 0.04169231434428866123, 0.13605890476106569098, 0.07857949175758316418, 0.13438815194241349182, 0.07649005138865494491, 1.07961019968133720148, 0.14551622632466504337, 0.50095836738235355323, 0.84696311646492705716, 0.88985683145058835919, 0.81207511022958067670, -0.29047941760174855741, 1.52154047648796098002, 1.18609259207615869158, 1.24147143249571478840, 1.32830110324834160807, 0.10599652429795269892, 1.39011659364140460760, 0.98247086829290442012, 0.06913215987691767594, 0.70715198246155519524, 0.00779946960670141465, 0.04766668375511051714, 0.25418417175439855793, 0.01989943256407444849, -0.05458969493066501144, 0.27180832484604677823, 1.41784285295022893791, 1.02839635456968681027, -0.37932235535564018392, 0.26704106147270828542, 0.84473163398931272461, 0.67123346605862632686, 0.21869796812593439439, 0.86987592771418742466, 0.54799659425472946328, 2.36320305302203781395, -0.22062169770541217861, 0.95758483701623009487, 0.89360676944037853442, 0.17211738299822976206, 0.94113083912150186094, 0.74673089961165195483, 0.19834014552043227608, 0.50031617911715220437, 0.74558544211606669894, 0.51743364622538301489, 0.31769814644764293732, 0.68355752328478536217, 0.32151759908601496019, 1.02142076216734190020, 1.10437801285216341540, -0.38183529795019205411, 0.17120229039097506485, 0.67787340868866907684, 0.72480975925500257251, 0.66052403917969060299, -0.11520845591626077686, 0.91868521551001736114, 0.52318914236745273083, 0.48238216481348950015, 0.40948925196172158980, 0.58415062302299280983, 0.06538055602943981803, 0.25726658711221050968, 0.75926281075830803768, 0.67281629341392945598, 1.31354865282771871016, -0.58622323589282976375, 0.10764209526086343516, -0.33018532702174457905, -0.23557439006845659613, 0.49762400579391397049, 0.43436146567378464134, 0.13246403285289096163, 1.33247546353195112090, 0.44977525465443568553, 0.73610854453687035726, 0.03328647783589916420, 1.04140658531356811167, -0.01612341362650820953, 0.11608876668199388793, 0.56613228435798246263, 0.05674125592601308998, -0.41420995067181770599, 0.77426087211157978984, 0.43661651131188439212, -0.22064815735002082930, -0.41922906983310770368, 0.25514496899572725042, -0.21367522264229085738, -0.14102092463460297322, -0.71222937787688489486, -0.70787954024958121835, 0.58870125820363949920, 1.22828843906329954194, 0.35102477298869710909, -0.09180164278387992205, 0.62562677755085993603, 0.01806743807675906255, -0.29224737674476014559, -0.37540776200320447042, 0.79640174936821683804, 0.80425227881894401083, -0.71385338121456354266, -0.54683163376401577871, 0.12740386756735036511, 0.20759702014260644765, 0.10256644134797097967, -0.12500551056084752144, -0.26889233244286137037, 0.04449151540131074389, -0.12900092314437977548, -0.54533078236499377134, 0.07435738117510856671, -0.61290136891792690310, -0.60205762115783134636, 0.39735274567025113956, 0.27989036858888333148, 0.52998574351956428963, 0.51441295837980915984, 0.86950394295991562110, 0.08297244566620466666, -0.29036892557822091554, 0.45814403593241914692, 0.06274781349866656588, -0.48471874474516241094, 0.24641590607968083537, 0.17898241621312177552, -1.07495012353866581378, -1.33463618906537528908, -0.41777975409193568446, 0.16467239341351053161, -0.66097319544744892461, 0.27828337388116430873, -1.10818044524459913269, 0.02871880860622423592, 0.28571975251297043741, 0.01432903230543458584, -0.33465221345662921060, 0.73638958876769966544, -0.25098137928995334356, -0.29629091483561420528, -0.46550032489633297317, -0.45840182771513704463, -0.33119359160565875477, 0.23869913807384340876, 0.26653716154331474897, 0.07449651298415335687, -0.02239559229003232343, 0.69325352297411890756, -0.81363131548324396647, -0.16779575156052117801, 0.15559230493812578611, -0.19228251287001596470, 0.30501502585685741353, 0.37938041396414290407, 0.37327155320047444853, -0.85445698050885643404, -0.43980317988083666991, -0.00868287165299591601, -1.57693336372284198532, 0.35655773632598380951, 0.50147731527123817763, -0.06435643107406122110, -0.78579965073210900073, -0.40658458983811868181, 0.57613911966039155210, 0.19874184487696988644, -0.01264674339278221149, 0.42094516293904232773, -0.20918962708945693096, -0.65157559375046081307, -1.26164113099699393139, -0.51992614020226157745, -0.12077571699657591031, -0.03107976655886074413, 0.22099016483827338408, -0.41831209537846553159, -0.73457549605684047123, -0.56814988129312016252, 0.22462265269172904114, -0.29728550759657229596, -0.96958951314352226358, -0.54182175446712788069, -1.34708222959571433108, -0.97079523463843631070, -0.58217350909456799801, -0.89610029617934638946, -0.07918387837225365766, -0.71709177263984180684, -1.15176978883291858402, -0.41971226599020672321, 0.20224057186903388050, 0.14464060937799716466, -0.24515240266265997393, -0.73189757024143464736, -0.40968336424402368445, -0.22005174786686998445, -0.33248587101627002927, -0.11249851083580103550, -0.72688020316567536483, -1.06299105490163836585, -0.57420803957781074711, -0.58997503731242106895, -0.03017299546285223677, -1.29758894869024699048, -0.67962765297621130500, -0.48652025867176240670, -0.84425796878772740861, -1.24612201119191556842, -1.10414785673067727956, -0.16812727904381019606, -0.28259008668392648911, -0.25687049461306243892, 0.22936039400719732129, -1.39373961095019671674, -0.63248832458859904904, -1.26130156161527096081, -0.04951846038531115823, -0.35986232960532715808, -0.54167806603284862632, -1.51558123134903599549, -0.50841324841302448778, -0.91347799005850682796, -0.19170389023447920174, -1.23208444325913735184, -0.35972895760563194933, -0.65181989062776035659, -0.74703638831235374962, -0.73764641995166713162, 0.05829062045130717351, -0.86842589040103446152, -1.00970429989819110439, -0.14631679267384278820, -1.37890946264223357431, -0.61718500260795816637, -0.46748452512607785447, -0.65018647205005786205, -1.49396116909693144059, 0.11911987240866217519, -0.75089284438697279089, -0.90404765294252542596, -1.03837190131528611658, -0.46492710790665037202, -0.73079407144126240503, -1.12835843663925272651, -0.26908412882376808728, -1.13866370257720905812, -0.07705231242622778698, -1.03222125020857191657, -0.66844823093787053558, -1.45547153747040169947, 0.16058415961523120341, -1.29397706778379584236, -0.43320281893123679939, -0.84037966187004486951, 0.63094573072151050042, -0.74755111262020090823, -0.70328849082361000189, -0.35866874218814664976, -0.63534342337598892492, -0.08637688381666297488, -0.60957174170076555697, -0.53145169396959979213, -0.49830110357566120838, -0.15164303253471878019, 0.11591515165350108063, -1.50655995923401575531, -0.58885407145073964674, -0.60754250330733294838, -0.54253926714200551018, -0.73495950289590294968, -0.20191437931665118244, -1.14737030718619736902, -0.87467841252617328784, -1.19671132167625682285, -0.88527383340645648957, -0.81924232920269512981, -0.51786710311461026102, 0.29365018843318224651, -0.52492730695787959139, -0.49372397917029198222, -1.12286563981230003151, -0.64819551676870346135, -0.86449357624342559880, -0.62287437121622624137, -0.32053458984275406962, -0.69171439739527218471, -1.13328680689938376958, -0.46491069014217917266, 0.02384682147538186303, -0.45227471489277543126, 0.41013835403750686925, -1.05884266098215706897, -0.88394265265068727722, -0.08544106871351758725, -1.27905942465029376365, 0.02181850551945013761, -0.27034677968531450887, -0.30237401666920638021, -0.21786919614472288753, -0.43471688899738636369, -0.13431841981893410942, -0.49787781999213998052, -0.85939748756583544953, 0.26254084910777270023, -0.35125258848647955556, -0.57400603286709117601, -1.25394975896733162912, -1.12695534433263455654, -0.02038668966944001681, -0.58225813057622655133, 0.06046180785818311421, -0.31443298748576159474, -0.68904984283757597652, -0.30263153024336186814, 0.18301174922393576017, 0.59065985679161525645, -1.21451828715343568277, -0.94898770477958660496, 0.80360755204466538526, 0.54719359036982284650, -0.10811032133433104430, -0.35794862723660836323, -0.55101499593035740965, -0.85945292165797715356, 0.12264599331672115801, -0.34263630615890688924, -0.70207941668302042970, -0.52663320445246286639, 0.48044785769120301744, 0.67877483915393899139, 0.37964076595953594184, -0.99498379480000387343, 0.06826272599467642088, -0.78251811748459598750, -0.56600582227183560136, -0.13496981987158701566, -0.62585640858687163757, -1.38425225342467639322, -0.10947707371136566890, -0.19166029382630336841, -1.08699321150281802417, -0.61606379575504066626, 0.34411126442513895185, 0.01199486691759543699, 0.65783386500470719671, -0.34592391856648818660, 0.41071327022567660769, -0.30662632831484248896, -0.03133610294753032266, -1.25417134197607760271, -0.89148063814124245408, 0.19314213709832558918, 1.00211226212221560239, -0.05964936098329513131, -0.04206515320877264597, 0.44230715237667578288, -0.30932952198550844880, -0.63272713052526863287, 0.32923836150626106800, -0.18445299475602366934, 0.08398245301581493127, -0.30690032172584186254, -0.06069937924380600786, -0.15990420958382189909, 0.60138043265343066324, 0.09684213265731218823, 0.08265089447861780070, 0.20133933823406621744, -0.06609305296836159860, -0.03333704550268735967, 0.56191569531056961839, 1.02177447442722280080, 0.58310476458608240247, -0.31280720368278963628, 0.78553065025672219601, -0.12766737789542473291, -0.32115483358742502595, 0.81689887911010639066, -0.40068981418144078432, 0.64753029371258796765, -0.29785888727658221820, 0.10954859395315809378, -0.03552580398416925467, 0.73347859245946067652, 0.10692663603557045482, 0.76912771379178956899, -0.27710453990873096242, -0.70565954772792949257, -0.02299178472810253782, -0.33360484668973178213, 0.58955073259092261040, -0.06663817651171272116, 0.50489841069354157721, -0.27878293915897200961, -0.50150219095497838850, 0.94773757001654845844, -0.21965655913268120059, -0.09192398398563308981, 0.23578689335862321186, -0.60372475970468264528, -0.06719396330879884305, 0.92357896561708741068, 0.17637140370448731175, 0.21830702090194911702, 1.04375828210253240869, 0.90488511731467347055, 0.85078588905980412349, 1.15198731356439165729, 0.39061150756887502711, 0.78501446460563883978, 0.68830072758619365025, -0.33774688667924901431, 1.13155465436158864101, 0.26121579042667647519, 0.50456329446879166767, -0.61719193943757499898, 1.35984384161013305281, 0.97349625564216291096, 1.01720074254204506659, 0.62155184490221304650, 0.68614314878066151593, 0.54554868755981111850, -0.02928828553174006588, 0.38714918666180453410, -0.27201248431566171782, -0.22873756401948064365, 0.68771752407267827678, 0.12062954827458016727, -0.66091617003197411506, 0.00642956728510013509, 0.48043962405985496389, 0.28302447579752365581, 0.63773883854687529738, 1.26629137250266965431, 0.91425455346685169378, -0.54225353329572834049, 0.08033876053017863228, 1.25411528408185235506, 0.55839458575588496458, 1.43771057452597506021, 0.39822888454703053718, 0.91767593267319791384, 0.10720108738578715268, 0.65148948193115530092, 1.33237112105828225950, 0.55358283724255985536, 0.15317684866007896805, 1.05964156312976376384, 0.62914219259724213362, -0.07141885716422158303, 0.46089726399160263481, -0.71495783535525980756, 0.78919103597411921491, 1.62838099085323162463, 0.76650199704599397688, -0.22246454233306400816, 0.48163747552950247899, 1.05328127047872088795, 0.01325057079395075554, 0.05610608598194027907, 0.97450754031513753262, 0.31347033344653163489, 0.53506168399436981886, 0.39407747923823421843, -0.01907731912921262207, 0.41952512633115113250, 1.16235323181025762551, 1.21713547773632657645, 0.50251930895070417993, -0.13090054161646869080, 1.57532925055400574443, 1.57147694120493808256, 0.94852403839313548239, -0.49846675542138618731, 0.82642493159215490994, 0.48417555276741047754, 0.91603111432040462692, -0.34195100288045388037, 1.62234403720065500920, 1.52325533058728579405, 0.81493966718099064561, 0.30119165613939730131, 0.66928539915397033955, 0.63391717262716018588, 0.12632760333564402311, -0.08206329822976987654, 0.41470443904485310149, 0.27250316250356576564, 0.71912811634924023174, 1.67980881083821120825, 0.89244581197581673848, 0.38222204850079249949, 0.38362803468023576325, -0.47508884674150919647, 0.95915048820220749626, 1.04870961622276492875, 0.52164194659818541311, 0.63375265542190506451, 1.35563908424536450070, 1.09349400441811828166, 0.00178684794744898845, -0.23842942541365330644, 0.40214018900435882786, 1.24229316496768715794, 0.16709297628539243608, 1.55725100441283803576, 0.54360905418339411366, -0.53998341415931272458, 0.28954104538810404579, 0.31189683709821969249, 1.07258152093591130338, 0.00048099284755986904, 0.22581570002344347747, 1.13489878613351202752, 0.95218375612693917365, 0.51244124416363545649, 0.58536062774153008181, 0.78444124532228853841, 0.71778164420748125085, 0.79572398118853837623, 0.28257582709139084098, 1.29540991741921418523, 0.01367376284873855763, 0.76272261193192636419, 1.46651600917420155135, 1.16412834489143612871, 1.18646304249849610102, 0.60826080457589659645, 0.74602678564782509696, -0.08265554066727420768, 0.80782317334880482385, 0.99955078498264748887, 1.23984837784650858694, 1.39366292459884544286, 0.09207659484197960875, 0.14905012545122947598, -0.00512143679836096766, 1.31218276067172534560, 0.67240086341865135111, 0.68093044385638967775, 0.31738976247920025475, 0.27427809502334310476, 1.24079546962610742789, 0.37550092598552298195, 0.45624901790513056055, 0.45606668630333224712, 0.55103850493331174576, 0.25660670828397402765, 0.56609588806401378580, -0.06185526806813457235, 0.57352236554259206081, 0.16655704178042457508, 0.43184350227725881721, 0.79470821360162546831, 0.25090558568633436476, -0.13202630223183481339, 0.50486547900442957371, 0.23043323581600802519, 0.40750354123823012698, 0.27469031141976779642, -0.00094078806570174756, -0.07479648303682956412, 0.51268339554202269071, 0.29129273177258241390, 1.22180172650963081438, 1.42380286424869950679, 0.46872830418084754367, 0.47576103443192041542, -0.83263693920003745141, 0.45339148516507044029, 0.64893870533292585812, 0.10590050162655395560, 0.80756890720165708242, -0.24388570511137186436, 0.00366169720765419093, 0.96103333158949966197, -0.52538710539459221316, 0.61794261405587569413, 0.76561708835045427790, 0.32776543596695273397, -0.48963734290417848527, -0.62427872309178389365, 0.49878576523866702264, -0.09682472033289302171, 0.74160022844627859762, 1.02456540913252291958, 0.62746717898538018066, 0.36710751054917423186, 0.59224336707006264291, 1.52129131528252359296, 0.38831472934140742748, -0.26385428800894417112, 0.57380015628165526032, 0.68243493285244716251, 0.53932893433572759445, 0.09705281496844928024, -0.84053609619691360688, 0.41050528903058425279, -1.05506346688554564217, 0.05580767421431988284, 0.97197264320707477125, 0.70578089504171803714, 1.14646274384304347471, 0.15196780582447877439, 1.05968523827508187551, 0.22370305507448634552, -0.54705821232869433768, 0.60328701864039491198, 0.90263334892858781977, -0.73986548241509897395, 0.18428539621367381884, 0.36950257715402728964, -0.11552890280969553560, 0.46476875760101676871, -0.12714874846106771766, 0.61384363536869368527, -0.33666355308531009660, -0.23162828474755617347, -0.33928929066635299394, 0.90312344043039971542, -1.00349903122936523481, -0.52979562289732040625, 0.64924894064512594927, 0.29914794498663155320, 0.49560009878918293014, -0.75690282191751689922, 0.63561271694888132888, -0.10226733006363800116, -0.11784751124016790147, -0.06996762532048876604, -1.00957240496821643028, -0.54855913638107289820, 0.18160240309539277259, 0.23638616289395486536, 0.50992609024817037167, 1.00250201545518979884, 0.02542877767042589499, -0.06679490093382663141, -0.22973006348902141882, 0.13040610520150747176, -1.07661329291892693405, -0.38720183235438754288, -0.55839026304955607927, 0.44006561680102929124, -0.70828990435483807353, -0.05896388715292247285, -0.75497426464476702357, 0.55667473346386431299, 0.24472254869839288327, 0.30811256079273585673, 0.00000197236482604723, -0.28098041307065763084, -0.42764414926565019748, 0.07558178236232498959, -0.14117442098115193239, -0.14070093300206934495, -0.68988920837257938778, -1.01851779395660702043, 0.33782144492909321754, -0.75508815515244154248, -0.05486187355914592945, 0.08262600840677369884, -0.97297655786871972694, -0.49959519695492804470, -0.61606104849254994527, -0.32920718096137868702, -1.08105714960566423599, 0.01142090851418148256, -0.09542540138828087271, -0.04763697750941281450, -0.38026049607131434671, -1.87915884230737662008, -0.75502151899814395897, -0.07307157918894086057, -0.94383785204089509779, -0.20717085547088015529, -1.09655300612635997481, -0.51612179216765674550, 0.61592404266270783797, 0.56248427493269936850, 0.32989712549199884384, -0.67958103398442193388, -1.30372091165572889793, -0.59464804678391436354, -1.69021414739191788712, -0.85584902931426087846, -0.91136445384181552498, -0.39112498051143829159, -0.47515845193337535868, -0.39574300907052090137, -0.43819469965457363836, -0.96647125671659472879, -0.21989999453963643417, -0.28246634884997717796, -0.49831342023392410923, -0.19393387296702602196, -0.22890821463362159838, -1.22501295994570025272, 0.01345235638101005549, -0.64853745184623712294, -1.28907715151683399668, -1.65002711659909540032, -0.40958104827608171616, -1.05474119111329844856, 0.01722940175614107172, 0.48068620245888271647, -0.84223776729967814170, -0.48403188292036536389, 0.38129664463682577669, -0.72327520291630797988, -0.28220399086946296174, -0.76492520623800586144, -1.78730352805980619024, -1.13474504156034505797, -1.43048835588672007546, -0.28221543504172991179, -0.95385846256577910829, -0.92421343588049464923, -1.30514221289106790991, -1.20418570365081434304, -0.25184141227736817070, -0.28491934472555041635, -1.00462399572016325422, -1.11667095327432130958, -0.01350202673328670944, -0.99207146909789623024, -0.59456821748690469320, -0.24142768360518546134, -0.41347303218147146708, -0.73349995002750223438, -0.69259294387530723203, -0.89593719806828964192, -1.64957966739294015213, -0.37413550446177823439, 0.18169656363663699850, -0.29142682121543322937, -1.08944173636962049478, -0.97512859848313848676, -0.94978907853243743631, -1.12820949875102050086, 0.07380393328816214904, -0.73848839243490616013, -0.09721025639975888755, -0.52652271543630535522, 0.03248029634876592464, -0.87824955293436424153, -0.49169608713645518439, -0.71388053275217822069, -0.32921277144855937147, -0.93826341148163616701, -0.95531415391593577091, -0.94389656669843668979, -0.89672534953564353444, -0.12416448128804119522, -1.58987184315069751683, -0.37342250067582744011, -1.35850962185880907995, 0.14585925866775939497, -0.41462993184253482681, 0.11855978449703930000, -0.33972820160481559393, -0.74252487383270460164, -0.38525200287894567630, -0.36579857197678888880, -0.85165449626776235093, 0.24877031943338034914, -0.93915881018526592428, 0.73188311923960869265, -1.27344978756978144396, 0.52210707483959239994, -1.05908169067071211700, 0.30174715808697716302, -0.65603735793404083232, -0.23399557245197843969, -0.94989508689135226938, -0.42602435583809428188, -0.81602189014856918448, -1.58655552378472775032, -0.05530626006327366007, -0.83761272695863298043, -0.03666486301449428664, 0.37048692207549516198, -0.59960563670078270704, -1.82368635285892044529, -0.47619743330154601368, -0.78877897419823217451, -0.72313903224706255202, -0.65689178674228121402, 0.39276457371600348445, -0.67660514112998826342, 0.06077333155915864982, 0.03434277297763105308, -0.36962259335726715959, -0.14244709585351394976, -1.18131891396471733202, -0.61596868986906894161, -0.30663010082855440830, -0.19686255987910017407, 0.23884189970881342102, -0.69623821080794112692, -0.92835983070313843335, -0.66382164376447794218, 0.07095814545661577322, 0.15146280866471378834, 0.08144289896561801578, -0.91392496268612499755, -0.32033588270097601569, -0.58145092427620859166, 0.24957420588119494376, -1.82380288045068117420, -0.07750740586904930618, -0.55687129106069632734, -0.69688160400208909451, -0.33769632936226823272, 0.69217262839731952262, -1.05784499824666755607, -0.70880091017140123277, -0.55457909309685260446, -0.87554316225302675214, -0.64160680930246827280, 1.00483851238578658638, -0.08229692970874302738, -0.68460794925798251764, -1.05626376551366285561, -0.31002107885510760132, -0.06383111998911439322, -0.19504032868600740258, -0.21157799616748129701, -0.47433032032624167229, -0.48953310695065227476, -0.09208275751910452445, -0.50530043691318282661, -0.81873076413158452080, 0.12343075475096243476, -0.03607436786784401495, 0.11055089696335781979, -0.77233082513969952831, -0.25793670501722448796, -0.05855458533866045312, -0.24252691612471949378, -1.14042120263819124482, -0.52717983093701092923, -0.65183145191114966188, -1.11616820815782680221, 0.76246660632305207628, -0.96867792953436071546, -1.54696539636091245917, -0.51073321486204359410, 0.50853787519145365881, 0.45324167178429586311, -0.53324800633904934077, -0.07018280744710427010, 0.49801804471214539127, -0.96290644113068135646, 0.04326208446970858379, 0.15170693981411628726, -1.72378748490882349742, 0.27758238114435318522, -0.10812517275789169935, 0.70467977289127237128, 0.43001867719442304905, 0.55810451763491319976, 0.03473463868145259398, -1.20466651966684756836, -0.52621026238567758515, -0.41330921494040517850, -0.18480398708459933044, 0.57599605444612822502, 0.45177980649122473356, 0.87970568980822672067, 0.01591402855104453312, 0.24417758276118234351, -0.00797905488325316437, -0.14964534431297882300, -0.07978071288220574264, -0.46048248515536621683, 0.13266940601980201087, 0.67534990290186180939, 0.40343029950325048905, -0.34128315978997247893, 0.01367929418145961973, 0.33977721655374359955, -0.13800976230042910631, -0.07499106772382824193, 0.93655052504775448963, 0.18867941427696238721, 0.62136228973850171631, 1.17156569459184045634, -0.11042977739462614406, -0.23514044308736736788, 0.17847007965549699371, 0.80564831118028923385, 0.46521312498358913246, -0.13563028361698178936, 1.05479197323770201855, 0.66953997067834369883, 0.61894681047085708947, -0.07694332551318933122, 0.01141656220977857461, -0.28492216661152736101, 0.15583592108799326770, 0.32118339039020177283, 0.28462770473423126827, 0.70488432267856904190, -1.13465530869559350080, 0.87639083540584761245, 0.75091481495341771968, 0.43880113814211674228, 0.04920557228459673804, -0.03969356694298381560, 0.43077225178344236145, -0.18566331165069371867, 0.56362184287367556035, 0.22300651095471235585, 0.12369199611610418033, -0.55761821996417992864, 0.36639089410500647048, 1.07840451232046841135, 0.63403326582211005924, 0.97253991682721818712, 0.42707779378515592361, 0.43198628273395783816, 0.11654438960002677650, 0.37845037836301131939, 0.43123491844708461418, 0.11227097113167194831, 0.36186131950886524233, 1.40866417117738063070, -0.25951395997535564586, 1.16182568922902151876, 0.08011431456262069206, 1.06059081548725742117, 0.30776003734953888413, 1.43015578986521041394, 1.62465314081505374233, -0.61061676079074744816, 0.71363876767599943030, 0.60573964386053702480, 0.04501264264660331804, 0.67258651933508406540, 0.25498372610181085918, 0.83199582691765594511, 0.00489944780886319986, 0.28790234553619159508, 0.41040737167903729166, 0.30807225918375424900, 0.98433965796534772519, -0.16627231716854828925, -0.01709653686559187147, 0.36758620853736684309, 0.20386455641375417214, 0.49082907878153925196, 1.10871074417584103422, 1.32436528465590619419, 0.82568597636396257045, 0.40321049160437416603, 0.72101281230588831761, 0.65823810010404459891, 0.33562087969121057185, 0.74348781998374757762, 0.14730937863433984925, 0.42918147767376546575, 0.01030627058720734457, 0.58337088113365276332, 0.33647594839339250994, 0.82704347978958048238, 1.01128099786961556106, 0.26846003209905699993, 0.59276079192040886934, 1.43127465523472219289, 0.13761738199376499781, 0.84912676911295803528, 0.53386175840433192086, 0.30069907013969443543, 1.05280716994287448784, 0.75533588968334719471, 0.00992450694737112560, 1.90975241735985323821, 0.57712831748943671517, 0.38916893930521212486, 0.38479937659197793742, 1.04484374836825377741, 1.08653615107294165831, 0.96492658140021947411, 0.62882004594186313717, 1.34911972407532188001, 0.48609090908949903476, 0.28258976799153573412, 0.61639651950399798608, 1.25416752402087583462, 0.44975430296887247694, 0.78614850689741611056, 0.76177513383858719287, 1.37912588068389618812, 0.73580787825345161934, 0.47423507758341754137, 1.65895417088199748079, -0.16807947511673104746, -0.23899548676142823300, 0.78412675480287574725, 1.51031134394580868197, 0.47550821546559290942, -0.66274178768636171633, 0.76611539330760869415, 1.08013466200487928681, 0.86052186266252506108, 0.17932103478224814541, 0.86952824793515159918, -0.04946550480718925336, -0.03559415580552616198, 1.02718362082774250510, 0.42358236756494405117, 0.61558074747475888255, -0.01507516121431662359, 0.80743845514452039502, 0.43885993903706654873, 1.79982025208062346167, 0.98631813238239507413, 0.35200123188477289160, 0.77572279345978034648, -0.81424577512997364792, 1.22888165269400473356, 0.76007138080354352816, 1.12716338233682589198, 0.55809763361545672300, 0.16527002699172177458, 1.17419400675826279823, 0.09298151086308259483, 0.87192582716210820237, 0.99922591802864024757, 0.15868098447366191239, 1.29212142689289244402, 0.90880265730764109300, 0.45245119805135280977, 0.35022420335567899796, 0.98514642917703532099, -0.65228199156064858855, 0.64602419404499200617, 0.33796985040007954115, 0.52006289035073771654, 0.50328007743885005976, 0.74721579426532702684, -0.07257496612794289703, 0.26644341769301815859, 0.86535745983384992908, 0.26093936045179333405, 0.78591846917599250677, 1.04670562594607163831, 0.54408885202322265773, 1.04374355446341948728, 0.01448053160876916490, -0.12336512160892837420, 1.33829835282016840203, 0.41014621960765584863, 0.07151320763395407898, 0.06315403377643458160, 0.87760739253150088501, -0.40270703817933400925, 1.62541997882980115975, 0.23105202957141268705, 0.82945029843189144181, 0.31530198928856595364, -0.93439884537516615381, 0.53245710630627096638, -0.13220836652263567856, -0.14039005674538701518, 0.33761448528877013864, -0.12885675302935967190, -0.05486578699551059035, -0.10059034515546089184, 0.49094767560002661178, 0.97932966151955558320, -0.35377620014297556095, 0.94764204403496032914, -0.15175211143681649473, 0.48641378456256734175, -0.21488562819823497918, 0.26557592210426511636, 0.71911080516017067410, -0.93573320027645268127, 0.33673570907152816645, 0.33838099682336042484, 0.25839845671671385352, 0.46392199132560685282, -0.06170274407260842309, 0.45125496781321339235, 0.03798276460498456464, 0.83889434597459433540, 0.13598356975982406336, 0.00709358946511109978, 0.91445532971103604680, 0.02117724551404381872, 1.24458098478484435745, 0.01412004334099348224, 0.12577054954001415377, 0.15478161151970201703, 0.87996170162769549172, -0.11770775487446533125, 0.17275279560723083572, 0.20572037557985500822, 0.71209880093604649431, 0.02378765514589539776, -0.48555167812791322213, 0.43780758955623610973, 0.90342315139553897030, -0.79323400177754810336, -0.22834180754702432248, -0.39713355325044263910, 0.02061566332444270122, -0.37384799399886209370, -0.41930019978679000658, 0.05839876839480294402, 0.20985490209849821941, 0.21689884992701180533, 0.57998493012774454680, -0.57637189663675081341, -0.24937955312889742432, 0.84127957181620438565, 0.02935603413575149645, 0.12844868676260381979, 0.65936858139843446125, 0.59293261881326275731, 0.25345294733045298230, -0.23345801856603873303, -0.37325906607648334790, -0.51520941799471953182, 0.01286200581951449373, -0.26117478904946134222, 0.28021983223729518553, -0.28453579786852789546, -0.24832102937366512529, -0.21698654885667184589, -0.54059938554171138581, -0.45955683104497602187, -0.29534814566928646595, -0.47637660079027094318, 0.90275079388758028287, -0.27106109697840180539, -0.18380375345273347598, 0.21608324604203818509, -1.03067538748592002484, -1.28985131264206964730, 0.24035751185528542173, 0.07455855047660278623, -0.01993161422876671418, -0.47736316518498655981, 0.09717824399787458600, -1.02776954768996620082, -0.82512460484267169392, 0.30415742658326183623, -0.08054891079553022637, 0.21506022653570414094, 0.95117748387746359207, -0.90900583688350788947, -0.64122606625969535976, -0.82727981334471878405, -0.12816819328427173086, -1.21502755829493436224, -1.86853369328560425799, -0.07558089092910375006, -0.04798156726846242170, -0.03697257963756905852, -0.45381818327456824314, -0.05803337753596721216, 0.22601664350690570116, -0.30628227407516245284, 0.12148091646101727425, -0.16426715192936880428, 0.16666531715063476193, -0.35313673947063295433, -0.46773944439302844733, 0.00202025193480370735, -0.83857713770514608065, -0.89257678558882602715, 0.20326647381167811668, -0.16731350370167019648, -1.17462334077388041997, -0.00192568738810283691, -0.85650923452381799716, 0.22781698179847181818, 0.30410661681344092200, 0.00819386393686055703, -1.49539383971242401117, -0.46023062362172850737, -0.48414054356937058499, -0.05859578892365663316, -1.03301482484101159187, -0.55398645807113455763, -0.12737274450414787230, -0.59714963535922982452, -0.15378570656571599606, -0.36256605605045499896, -0.95466929931069732795, -0.83297067490645249066, 0.12056196880028446916, -1.42427084899784639482, -0.16362690890571318647, -0.24102879830628443081, -0.31944184555979615592, -1.04515093578857110046, -0.41642582269955685792, -0.08738115485798197968, 0.34483286738239304903, -0.68649453678271388224, -1.35802914298238430746, -0.20795471933336845405, -0.79037930050703564255, 0.01956299142109785283, -0.48382951791467149194, -0.92137070916707930479, -0.65053555158860398855, -0.70103859922000899552, -0.78431024565570961471, -0.58152929527230268203, -0.16873178100282448799, -0.66611560319328422253, -0.20057331163541969321, -0.65263203960670945758, -0.39697678844769512807, 0.15916130134325412460, -0.83085329918012595929, -0.51713130054995992690, -0.79530474755227398287, -0.05852715218530835095, -0.24234245254383013357, -1.02612580102125083847, -0.30162026568873445687, -0.28006314043562424709, -0.19651626569293123570, 0.39711132419236550017, -0.67261008516829401405, -0.27380875613023281101, -0.56152524599762210222, -0.36687050062712178722, 0.12214069654505865792, -1.38462057189789433664, -1.30222182044560819136, -0.62404462769936375199, 0.21925620835662440467, -0.65351763582521482654, -1.27903785327817987039, 0.12282396367900916268, 0.26252047888837382050, 0.19187615740778485307, 0.07441101636377811523, -0.32996650280539435718, -1.01648828065795671094, 0.27083059940367726348, -0.79306138679206439335, -1.46104108579794078615, -0.36066383273351604322, -0.14672606466813326032, -0.70955026086608630198, -1.23224622066775446605, -0.31784206698791683809, -0.94298241282541428454, -1.35663862820239455864, -0.27592123870227369675, -1.41398933881977439952, 0.16202038336831758869, -0.99754046185378197897, -1.87524272493903176873, -0.28889527573184980991, -0.41019363291498289747, -1.40792397210534359075, -0.85941614424177603659, -0.51317626221622680838, -0.40671209830126192619, -0.51831695355261053759, -1.16739702636813968972, -1.52887868576663965570, -0.25565591535825732405, -0.54466302423718016001, -0.38488550854554642866, -0.44562320270996169658, -0.26245515722232493072, -0.49823845632144531104, -0.31510013551152615818, 0.30378194926921031627, -0.22389433756825088961, -0.36305783661933055395, 0.28422101220634676810, -0.98028104743639987717, 0.83052848530922684933, -0.88037649685074104156, -0.53702217370343052760, -2.31798834295878020129, 0.32305546664285966774, -0.61105064537945330549, -1.10081014507531804725, -0.88355487218340389433, -0.84937484568863097500, -0.47115269080145788516, -0.65592623556110918326, -1.04677659646064569188, -0.43731618303209740528, -0.20109869618667891578, -1.23600712709541049605, -0.00517788784688949510, -0.31994648487537946657, -1.18205157956707163081, -0.58853090946501851466, -1.39252374219917074427, -0.25301883597005003779, -0.48462439608794211088, -1.33299380610334461394, 0.19234775151522492953, -1.32672597936276370767, -0.87847617781314912833, -0.91441416817617582335, 0.32294331833573330925, 0.15226402908241498668, -0.53296820594977156738, -0.63420992498709094498, 0.24677487565345440634, -1.56538935196946793482, -0.12204722778163040742, 0.27857609735299460052, 0.09307957780846093376, -0.34858001279214290147, -0.88617027225337130503, -0.96611707279318337349, 0.04326732740884914330, -0.34798742696688406140, -1.21446584957774650348, -0.03054976404243353594, -0.71631322227113769507, -0.46396961714097473273, -0.17194456753649167702, 0.32175070937236627255, 0.20625173274091851416, 0.11860886336807469066, 0.78827973043268884101, 0.25840317487069053293, -0.37708405940148492785, -0.24108341504779765008, -0.25053711525467514676, 0.35156775921069066682, -0.14900049379036059416, 0.69012884849533884246, -0.50322561899931317342, -1.28840587938485495734, 0.34969651199160806110, -0.05757674458791917083, 0.13802702178745770989, 0.72535260411071977860, -0.23645310959143381324, -0.72931288363219537452, -0.56686083922175689587, -0.41494948958014543638, -0.60123435254093238189, 0.15228234937320705988, 0.55127828129683698055, -0.80486636641862974173, 0.26813173605066670246, 0.04386317506322691406, -0.37670189687568639281, -0.35490196817744290581, -0.74143900121055306141, -0.05321219481705496968, -0.50321707396384851574, -0.91207261758764557324, 0.43787711451407890229, 0.49622349084778061279, -0.44032240243907155852, -0.37634818764526267731, -0.03964799084847860783, -0.23380336844063226431, 0.33474394982695609979, -0.51919128174846918000, -0.24908440454621466609, 0.17933657086140214476, 0.81345418242613665161, 0.56496710044017506291, -0.74551203320167203081, 0.25806413907239239558, 0.05625777419302370463, 0.08822205467649174571, -1.06230775953603706618, 0.25869941493341158667, -0.47274872444331927923, 0.66255465839633809200, -0.38243476906679840788, -0.37066865741184684691, 0.22154107363772707062, 0.26315721272599607028, -0.49123629140254798653, 0.07351702553482492020, -0.01146649336196853275, 0.21700010794127849723, 0.47401521063368312614, 0.89311177020357168654, 0.07953308498025868367, -0.90331509112497365699, -0.31152132935978987804, 0.37243815529088497573, -0.28289499213999502736, 0.37469828622346751379, 0.14817974747163292770, 0.20437834933217560729, 0.00531102956850060259, 0.18931191713613770844, 0.09881490084665642271, -0.69935290016500539778, 0.84085473948595423899, 1.02128636298967112594, -0.60408367003135632345, 0.21159853022015828738, 1.08109505786021919960, 0.34252045732304492454, 0.41879371462891556988, 0.02294822870683188576, 1.62521892881429708133, -0.43545499638636708806, 0.25319156676216342250, 1.09981831590957646050, 0.10455178598353145891, 0.84305670439883229061, 0.32442748048359343338, 1.03138096788507738211, 0.10150472227551410320, 0.10086361411095529084, 0.93475604888389629554, 0.03562126638742663109, 1.13523186092234240618, 0.49888263724270259791, -0.50903159320404411936, -0.64403606566861015814, 0.13121607034699123151, 0.84400409540417542509, 0.41898506149208941673, -0.48499582489532627738, 1.12283488790735952456, 0.19123839442101228658, 0.82335948611366382988, -0.36643255272108588017, 0.53364551028012885414, 0.45040874613087966694, 1.03203888689248990396, 0.62764239305754587761, 0.99269936518173762519, -0.25616930375563184974, 0.55937251732855219899, -0.16533257982539756314, 0.19014572735390972147, 0.88870991890885575604, -0.37247525275100051845, 1.14229778849182084244, 0.14516576486334242801, 0.63993453744662454064, 0.16666046026374936595, 0.79841389781111415935, 0.59011361532491335424, 0.24229411748464740883, 0.60308757664594092596, 0.94536348956654658870, -0.09151479912804894035, 0.05115964808768513894, 0.07549507082254997004, 0.23924249711497064252, 0.23580669512278595867, 0.11175406822094513526, 0.39212787300322138329, 0.26248448874330421177, 0.35198520059636662083, 0.12864073127174008304, 0.01764971863331066260, 0.33206000921842226958, 0.26994419989376672309, 1.36588212366423089605, 1.02209422584547304780, 0.44314633908345246738, 0.70749000774926140700, 0.68734426395839465229, 0.36376495865646368832, 0.48833361163365346380, 0.57576692294019915508, 0.44814907123732067307, -0.31746551946133561017, 0.26967089337041427743, 0.20739754543659805197, 1.13990932213584517108, 0.67287890422156571013, 0.70383424830680996198, 0.33804740133489935561, 0.41271340359150282540, 0.50070346811109067708, 0.71949192256658023314, 0.79054510433256330870, 0.27478839314136188632, 0.80362508820724198877, 1.00249843792954163035, 0.72412050476898448537, 0.86504889587694067110, 0.52469921360704363522, 0.08955475113184496738, 0.69297924197419880077, 0.86636474607677471660, 1.46916645379341925803, -0.30645807098780641908, 0.10836385419480337733, 0.03169527638655100787, -0.00244593436680329290, 1.29095835941052072826, 0.77806676303842015052, 0.04665214445279530775, 0.93834807524217944685, 0.20394051966935589082, 1.29608496888845903250, 0.50656796676010773517, 1.20548931477196941131, 1.21195116984346684674, 0.30208212886326557545, 0.86355166472043976000, 0.48170411391450146388, -0.18606278757113003941, 0.73118233798771137266, 0.24082112613499284670, 1.39740785556947999524, 0.14823157042600365596, 1.10374947451610561089, 0.67141332927537233921, 0.42896651459951817831, 0.11585867557291368302, 0.02025196982308774540, 0.87949983088201522108, 0.70722515400354080128, -0.20938793736938265955, 0.92721605355475689159, 0.74233882407211926324, 0.65772508458572453982, 0.23801744049853934548, -0.06552051698432403981, 0.48214578227044124770, 0.61780375643297391619, 0.92490262780595755565, 0.03983626673991025324, 0.45618511195444022555, 1.09168723925500144034, 0.11694088061931323663, 0.48990300130920061417, 1.15759314421506820381, 1.19256185829849026270, 0.88067457574187535396, 0.56526514196434618498, 0.09871458637333468955, 0.42667472937270994882, -1.14239965083009087365, 0.65015268450762420116, 0.22159227614514509508, 0.82114077204793445297, -0.08914757906013109912, 0.80454904643102409523, 0.19532780902355337260, 1.00021273341871674845, 0.25185440368758438279, -0.40682085230824821531, 1.40562534640960978827, 1.10826615208296530568, 0.83883930213873303394, 1.03431041393520439797, 0.55571883727503657013, -0.65308479789433471208, -0.33622646710646331147, 0.84415917149898889171, 0.09643757256811769096, -0.07541293080151706407, -1.07290905313022655321, -0.33661759697177462414, -0.29123950769414819195, 0.73591664128035283454, -0.46072530910093945078, -0.34073993790852791230, 0.41425507394483740420, 1.02900958585784496613, 0.26234424608285017433, 0.41211419243271690815, 0.41212909865620939165, -0.94962338405640323025, 0.99300708254185576784, 0.05133483566556823474, -0.27641188138071348668, 0.12076965043542456368, -0.71027082055777723824, -0.17008717674826184796, 0.22506173231091711195, 0.19985767062765538715, 1.17527470989170756965, 0.39654977464903051754, 0.48356107451591201496, -0.06607842080481310387, 0.09965419835180820696, -0.28670631563197113145, 0.73701493699806586157, -0.27730805575693240339, 0.87992804415498726556, 0.84166855232984705282, 0.30432859440612203272, 0.16827061374703256025, 0.87975958857416058922, -0.22929672353222066428, 0.28345541586034989079, 0.02489144816407777572, -0.18775620013395211139, 0.54992498566390224379, 0.55572000899556439624, -0.17824436200059323721, 0.29105047728690153219, 0.03402197709562670286, 0.13380911274415691059, -0.53526837759219625390, -0.21510094082688926975, 0.06717086956184459279, 0.55824457415600592380, -0.12019482904848685645, 0.15034725016989014534, -0.44357512515051933377, -0.21119268540890215324, -0.32402799132275117433, -0.17887897790532275577, 0.05494158411650425233, 0.00320956963590604896, 0.28879724444747301160, -0.78005328575492915988, -0.04742502830407227532, 0.39617374897024310032, 0.36527337195289077965, 0.14588733467161385526, -0.28665282290408511390, 0.30363897230356629020, 0.76460386513654454710, -0.81368102542418940804, -1.05551943007042869915, 0.09219118911536607064, -0.10075265477477876597, -0.49179015100809569949, 0.41974325335623718924, 0.26175581773169531719, -0.05376893920893044410, 0.22792777533156105552, -0.81412859381531332836, 0.31684369243579268982, 0.41632482437946183307, 0.08754396855479915085, -0.25978643697908798682, -0.30082077821408703677, -0.27416711138731719721, -1.78362598438496289255, -0.42477078257004541317, 0.00637065571055872581, -0.14571606253762264815, -0.45543314552439717602, -0.37877538227685392247, -0.09791070595032150270, -0.71431314014714997196, -1.05624756249894269367, -1.34046659380042343379, -0.04033521951705290598, -0.74943575607775614333, -0.20352936236396507175, -0.27489718564271797829, -1.10372538793186869555, -0.56021284305556817706, -1.42461902276996799088, -0.35214474830182068699, -1.00783153957391191646, 0.20157322080873690817, -0.58576257597069170124, -0.37360387280661777609, -0.63432515325522753624, -1.01837910684413146534, -0.35962038163294862692, -1.11918458002543363961, -0.04458972227554364176, -1.24396556109800049406, -1.10790914647211291566, -0.14572239056462454876, -0.16197454039813338755, -0.34868822285896788893, 0.27162211152782872459, 0.20715257262398423244, 0.33933519706696019247, -0.27628986578656439255, -0.09287513491886739692, -0.49679970174431287155, -1.18975646230093867750, -0.88516693221417197535, -1.03141661767107550851, 0.37217509467067433349, -1.37148824762082122142, 0.18793179525859465828, -0.71820423438085301271, -0.76852167211303457073, 0.32706468142289502055, -1.36836230015656612480, 0.02922063338038782820, -0.65813875584104664096, -0.12442077705411563882, -0.34305938230134658262, -0.23333178082270461529, -0.37306429676836350140, -0.04092468387457665058, 0.31255411461301796372, -1.60405862616270944443, 0.34222929873389662525, 0.67231143733789611172, -0.70438477621972872544, -0.34566221403666380629, -0.09570519956572126619, -0.07450025564053441851, -1.14526089871265779330, -1.42131572003562967055, -0.68045062232256492418, 0.08944469765436158237, -0.45130944243283344086, -1.21328164049858422402, -0.69153158593547703337, 0.35325314004155072833, -0.48004608210528976198, -0.78652647290389232637, 0.15651564816914298461, -0.97397573768256395788, -0.35781061022609844668, -1.46498138383472964463, -1.05726014031562631779, -1.03458112299587923211, -1.22280762206606730125, -0.67537298712195137362, 0.40305984166817721004, -0.89357688471113116968, -0.97234099327967504767, -1.36258121112159558130, 0.18603678492171449310, -1.06681725474310962198, -1.53258362946917348069, -1.03799366368870460420, -1.50017974636031659053, -1.22333163760278762489, 0.06179211366646586434, -0.67814269555239092835, -0.57257295371181937682, -0.91726877349082025503, -0.88412025857653631089, -0.62615655017233684543, -0.92900538937718568633, -0.48541311804440628874, -0.80780092572289907693, -0.74348165468614058060, 0.05803919598879703212, -1.37539314991356409479, -0.24455137833156298521, 0.62625425397179645959, -0.87935040201533531956, -0.02252054383442969598, -0.17898484168207162703, -0.30958045609644962992, -0.14397896033233720248, -0.24737574472234397094, -0.83706194764721009172, -0.30351018372377597254, -0.98084929244325569364, -0.81818255675163797935, -0.90614028050478434562, -1.10139089786435784291, -0.13500574278023552699, -0.80703528485501674350, -0.77946696177283836260, -1.11934900331902298376, 0.02010114358001080515, -1.04486559601635420336, -1.47004059843049517120, 0.09807008414678597408, -1.59881903722587415118, 0.67955528684140809048, -0.53483316154329751946, -0.89929703369981384142, -0.94911264412567952764, -0.83382776270525327256, -0.92339907898829742816, -1.00561866214440343015, -0.04431590214284497620, -0.95277431744654661472, -0.56490568262500950869, -0.25304061885872830562, -0.87102249165792156038, -0.55005028955563650683, -0.64013603572956301324, -0.95232917950393636275, -1.05289227803016838259, -0.58977989545389963855, -0.69565371946179754836, -1.12917484400572964987, -0.77019394741535907034, -1.12603775435224107149, -0.55907100054499903052, -0.76291182660757805856, 0.17676209023581990198, -0.01977188148449232630, 0.62449920819601301147, -0.09864615604017235029, -0.03504845453481320039, -0.76991823129240555268, -0.90725094196725730722, -0.52748087549770328319, -0.85583815902586701796, 0.16204231854666634183, -0.39724774584478084183, -0.57186689367800835893, -0.87775873577605811882, -0.22019771735449625294, -0.62338201717764663012, -0.51698371425561839843, -0.60948082553653282201, 0.68063745743221693019, 0.69527271207357110860, -0.24478918226165147232, 0.71709176034992538895, 0.67885024243637770436, -0.40967895182345165361, -0.97654156791675106319, -0.36340912655531776299, -0.76994441709271632668, -1.11242007073366955261, -0.44551855551581848180, -0.24807257703208984712, 0.09780316993329635222, 0.28596618049002930917, -1.08974379144683286569, -0.15680815365969380526, 0.16880051724564826277, -0.44769187053919601293, -0.31330443753300546428, -0.60211234523471990343, -0.66387271046415108344, -0.84182416229598588941, -0.49063857854392523006, -0.81743679449546802207, 0.70367175352337119065, 0.37782797062068723681, -0.07122916772005344344, 0.63736333929857647007, -1.01897126249321545721, -0.08896529951917886425, -0.26339622511603966171, -0.70457944040251607731, -0.20940259942737043941, -0.30375628262243126176, 0.56514865466982988629, 0.28232018011344484076, -0.04816300885540757137, -0.43971625194565722161, 0.71991859028890248240, 0.20275151304452071477, 0.74126468261480560340, 0.39475658129424906484, 0.31508341605738754421, -0.78636000759201918342, 0.83660021199207879050, -0.10754551945269591462, -0.06002974185331036439, 0.14117021505777804791, -0.04274000610469185640, -0.07364857673125785320, -0.13961340707077943279, -0.21583287560766067914, -0.24903809470744767141, -0.73014090345506632573, 0.17166927301545956142, -0.86898463071833953464, 0.43921485274729277215, 0.06841773020309892261, -0.41665437677725214449, 0.40817525859246106545, -0.39250824694225083800, 0.91239196308732961604, 0.23915201319627382714, -0.18449948597086349156, 0.25848721397091256602, 0.14114415684927178463, 0.66148420873412805410, -0.10023438389445323005, 0.12247404462545387338, 0.19572022911443037740, -0.30023939311569830535, 0.34217159880489189661, 0.39243529299145485378, -0.10676013223822111708, -0.54421854577405148756, -0.68757205329242243597, 0.44277820230724296291, -0.58852800870793631205, 0.33137224751337124307, -1.47060426021692181031, -0.29720603118010591182, -0.32281216352301767003, 0.18678932342762014085, 0.88741442234637024633, 0.04612629218569361256, -0.15326759886268606636, 0.12411758927497441862, 0.41413227152574472179, -0.23094493524693449071, 0.40312162991418054592, 0.02527896996043285660, -0.17114465894903635457, 0.43254455314991324677, -0.20279119949175761217, 0.38120891962296016287, 0.47251274544755378360, 0.60045706233030959886, 0.47210385889387657121, 0.22390956834195380187, 0.62360484853951014816, 0.30516032691645933461, 0.47014843785041515734, 0.63012544038922313483, 0.31594441363733322126, -0.41683074116598789471, 1.26079638029829599333, 0.42535157573735621339, 0.13347793372683305901, 0.62664828007571982127, -0.48487834415599545546, 0.02908969232912850256, -0.23566151359453391123, 0.36704483933373532878, 1.48913540743809158506, -0.29650851464975824801, 0.52381292021975445206, 0.49199656746766529114, 0.00925933492662506241, -0.88828343195507586394, 0.09927216749639838911, 0.42195267711922224141, 0.48043922770029767744, -0.18091940454152027895, 0.35802427275482018310, 0.06348978386319875655, 0.42015538627664361648, 0.06080090497266554816, 0.68561065054623249182, 0.51477820622770120718, 0.13701862246486157249, 1.05018596737236569183, 1.35650923707074699820, 0.65789892509302316803, 1.29441095023460595037, 0.05583016922617173305, 0.08652508209104631254, -0.42140517514673303534, 0.34786964129985775696, 0.68857266129729999271, 0.27628453733613445031, 0.53298374289959993266, 1.17715130292177061477, 1.21392916948041174052, 0.60516355612181316204, 0.00594305539096273705, 0.56910215661030383316, 0.85995564949010117051, 1.57148701818964786625, 0.07067870557924027519, 0.10478822089842088072, 0.67758754031034507115, 1.03463930915615098804, 0.46259515925193706032, 1.04357640788339622873, 0.15804205155316963793, 1.17333552335239010844, 0.32216447541563736356, 1.55482102005805700173, 0.43660631638378621933, 0.36537396190936022311, 0.76547056535247748332, 0.65333890160335816866, 1.23196275188733794792, 1.48449146318310298831, 1.32718933888230106177, 0.25710269382632122692, -0.20239994509206249429, 0.58881103814617874459, 0.27945212095685845188, 0.45744758200501645096, 0.60177335865411529792, 0.56904338167445389640, 0.82740854033376320942, 0.39503281258946820742, 0.05641165853020468912, 0.44602992946019459630, 0.39266782303456304604, 1.05728068323327417666, 0.75311803011758704507, 0.66295810157689427644, 0.61393295874379727461, 1.82314818550734925040, 0.78020249608108160899, 0.32705411426178560008, 1.18972418005913005423, 0.12206221860094734755, 1.08157983398197465874, 0.98503049136625653315, -0.20581179301690077921, 1.37467958580911009392, 0.23954437559728075646, 0.97015646510322151030, 0.88256466434257474507, 0.18451405889764105339, 0.37470246509026189363, 0.10236053473103551292, 0.18857237319300035328, 1.39242497554786970326, 0.34366513529933984117, 1.14540959562590050069, 1.03538371053131994515, 0.41899197828594691995, -0.08700388459246077844, -0.42595902510490568638, -0.08872084496678134258, 1.06339940851660186283, 0.99282346458758963248, 0.27313562355923326486, 0.20074654629991695032, 1.26238399239057930146, 0.70819101459772038076, 0.12808991481619563801, 1.20150636004724109540, 0.31096039344027737172, 0.54822558596788062424, -0.28553348766997477259, 0.43943797897860309432, 0.35298431774765826052, 0.56168723788704677524, 0.69054170143284443473, 1.32191199000613002212, 0.17045061835865987776, 1.08451327200174363341, 0.13699893585198247292, 0.89041909814809694357, -0.03217641200928822443, 1.15405298388665378262, 0.83049386613731468465, 1.28265010749756891428, 0.37311555503958682145, 0.39561099793580895012, 0.79834756261196648808, 0.52547386604089929563, 0.57845049325359076953, 1.27662158641604372988, 0.46308279461689372258, 0.91362150726508328802, 1.77746217314766097317, 0.07946539609826769324, 0.40852170035336793363, 0.72221074215964131682, 0.44363245612808255292, 0.66565087327216798663, 0.91689121267818707306, 0.19627456375391938437, -0.50009118869507807048, 0.43873757383322009584, 0.26352632214148180578, 0.72057978228862218373, 0.55682384706536813823, 0.16034036945921240069, 1.27492659048820766721, 0.08951552576629778768, 0.70346030790888902651, 0.69237948532621662601, 0.22745618421734686843, -0.00929141140843875446, -0.09208557254575083784, 0.66930850862262492029, -0.32338979697071340569, 0.81298571606961977931, 0.36029119394553121847, -0.01390812877280495075, 1.51394404480890165665, -0.27960639551938698544, 0.31840963265703531304, 0.93634580310330139952, 0.01186428651009180379, 0.26194802432604735731, 1.06676702693735081517, -0.06871467352952254082, 0.70395789998197333404, 0.15709508230453017585, -0.04422817873895174001, 0.04863746058904216962, -0.81385565633573553335, -0.56665427616937136257, -0.10338051488648389298, -0.29045619881251505401, -0.95413158333271519407, 0.45740218647590158429, -0.59042494714438631398, -0.08650437235024874272, -0.01999292646159464559, 0.21493825972990032724, 0.08005146213910688258, 0.49541520461585109025, 0.30795638135567060534, 0.18080378046089801147, 0.64455244330570604649, -0.21422390043207273358, -0.09158477036924229930, -0.28291236360795690707, -0.51569160628441723659, -0.50876942886347475969, -0.25949752026029843099, -0.68159527787070450788, -0.49324675493975495000, -0.32829181954718128500, -0.64970106113975267803, 1.27101609108677271465, -0.67095471447684262856, -0.77097968775861491775, 0.23075830071182265590, -0.14475472604462152892, -1.19748509916709444667, 0.46985715638112324211, 0.31429718934252848239, 0.15650599804572670748, -0.27383976700790413350, -0.72748362485000461763, 0.00922146531161965183, -0.64497187103838160560, -0.06501102186795156002, 0.41003777934870661026, -0.67285509578310498391, -0.46046918650052670596, 0.20836853325286736061, 0.44125410094283468165, -0.26545758743829200865, -1.27015398725786710088, 0.02098012999196843809, 0.49598964857338084800, 0.72045304653337827183, -0.79462947736439604185, -0.01195357199722901775, -0.74469296366616088978, -0.37456682064831242629, -0.07015470077748779676, 0.00128179893152863267, -0.57137509129217556314, -0.35971620661556963361, -0.91880199772353809973, -0.81892012783492496020, -0.48387913747624045069, -0.41004258227987194774, 0.14806563191557939319, 0.11198147604656022436, -0.53903525752391945325, -0.25627211371007896368, -0.56392113920449582487, -0.94327256823567529054, -0.03852763713116055810, -0.52281773493698591171, -0.81688357704619063426, -0.27379720486400743740, -0.12543298653575277180, -0.85374170525789794262, 0.73804439779066033367, 0.17740211975481312567, -0.55233381559691696605, -0.50670746523802523420, 0.14777782161748953538, 0.31934752953568185108, -0.59869075222917311763, -1.95237520902476080487, -0.54172666092645005431, -0.58295375695149642681, -0.43702709700270014892, -0.62011414630339289911, -0.55941353458510223451, 0.10049472772340767301, 0.41253058281021282028, -0.84054165657697321734, 0.20217791294070375230, 0.23926403020173597769, -0.56539057057152353192, -0.09039897662312967874, 0.63842582069973441428, -0.77193740845356528801, -0.20836303098123540312, -1.25353158722216373455, 0.30468108464984233752, -0.58029690726803839773, -0.09491790885065631844, 0.46622869984705622715, -0.03341346536636530251, -0.13295273332149099010, -0.70443508110925090460, -0.41674921622420058043, -1.09993948443814071325, -0.29869551420075723946, -0.11541497896918634369, 0.39125187155407337158, -1.07183372517032537985, -0.70806979522448143527, -1.26213509083624408902, -1.01629253822374088223, -0.42676808911622715614, -0.43933042642304004355, 0.07734481186366914951, -1.06028621046638771297, -0.61152791554962326881, 0.09712029610815697822, 0.08578016512470798549, -1.92725159512051669175, -1.34020840449938205552, -0.67872109226913990287, -1.10112722555928677082, -0.50298718278944887228, 0.06948856992044150083, -1.00796869436586278290, -0.41355447176456405778, -1.02910675439697096856, -1.35691447502734785502, 0.31472601142674039298, 0.05340604681242266949, -1.20577817629285699574, 0.13845189960605308599, -0.22363853024173008244, -0.48602406924071622774, 0.23800569426159345898, 0.04352238113961470756, -0.60309578103138483662, -1.64019700186444761236, 0.54821292855282188761, -0.67965442679793519609, 0.55859902185850440759, -0.40279816368564691498, -0.15987066085486267841, -0.73462352342451375797, -1.02948766189166640572, 0.67975726484913767678, -0.64239155921676938110, -0.64135190850809631335, -2.08330406537385393051, -0.67955917587524161672, -0.38265101928961564592, -0.78546501691669945444, 0.05683480744395219553, -0.40500528108451822051, -0.73733121585866201464, -1.59767398456508757931, -1.23918658976696982599, -0.10408274618061236216, -1.89385269557048729894, -0.03220365286357163015, -0.53686786151652687860, -0.62298983302215926727, -0.00539883654937600710, -0.18627445673671627624, -0.55231729687408293028, -0.60369866732770072204, -1.75145214532333159951, -0.37246522976157797613, -0.03532385238183133325, -0.82487428699918674546, -1.45131916186192211349, -0.29415251029121952664, -0.35229080597388040541, -0.88961670417165872315, -1.24161026942514163274, -0.75418483828400983970, -1.44689574640382412341, -0.67685188139538998708, -0.11017580738198612389, 0.38408137589249868871, -0.93683826584540597082, -0.23111053398240216650, -1.36225635180247994782, -0.06565370606701925738, -0.92941356963181176454, -0.36519151970683205732, -0.53594266141073254595, -0.07060528837489810083, -0.18960783108972789623, -0.79215626563262331317, -0.45848985524916785472, -1.41105221655465351560, 0.26142480260117773039, -0.65365639445028067556, -0.19953229800443189612, -1.04171212293487691625, -1.24864650715144565041, -0.56622294308908582838, -1.33435050316525716241, -1.01217532000348864329, 0.28512282101317776295, -0.93248586969622859755, 0.91501886287460942881, -0.82483064292539975959, -0.97445100598562106597, -0.23039279537400858544, -1.15758432365698715749, -0.01399181265754834147, -0.87893787202050088769, -0.76732529355591316822, -1.13810505382119986351, -1.29975205056650056079, -0.73091835081110589911, -0.30792088443628407024, -0.51544366391096629876, -0.46524921415381187417, -0.32941422904421996387, -0.79740594948699816680, 0.49960859247748223844, -0.33629618581476922179, -0.16847248244911192105, -0.39204184562809973880, -0.44248605466898860428, -1.29488165968751411938, -0.29773221458928150751, -0.83416948918946909330, 0.88146437261706722666, -0.37535875776168009521, 0.53207300175529248509, 0.38151481280545002095, 0.60435224350662120063, -0.83050243598551609647, -0.90841620366861186575, -0.07640890037637113053, -0.64760208098319727021, 1.04565147289446835899, -0.31591888195799527894, -0.40956593833390897430, -0.06282154996028335714, -0.46526512139339515350, 0.09410657337959671409, -0.79739765555173180989, 0.08116984837066093528, -0.29972590111691105319, -0.30066385735906631105, -0.24001334944262048277, -0.36146682771750465735, -0.00137771963931862529, -1.01079792578301619344, -0.53687193643200736837, -0.71249958568524673908, -0.15295368044493981574, 0.26934274001835067924, -0.31529941483094969801, 0.04080576897532278702, -0.31489125926294225799, 0.07908316802486370367, -0.67082520415535173974, -0.66692527476994101221, -0.53286786273650244006, -0.03045556918238066790, -1.01255351479069544141, 0.24844532255839574253, -1.27078805187023147205, 0.15205226859128812000, -1.13366611320660770623, -0.70489234626668717532, 0.50326419767004038075, -0.84932136469474983631, -0.47824335363366193841, -0.40886899757732281246, 0.04128260647015145890, -0.40893631206876002171, 0.17483990866653884022, -0.38019156581547142171, -0.02045846782592515567, -0.36471043679539605353, 0.07576455821916869282]; + + + + + + #Parameters + var pi = 3.14159265359; + var fc1 = 1000; + var fc2 = 2000; + var Fs = 8000; + var N = 101; + + #Parameters for findPeaks. distance should be constant. + #var distance = Fs * 0.6; + var distance = 4800; + + + + # Step 1: FIR Bandpass Filter + var wc1 = 2 * pi * fc1 / Fs; #wc should vary from 0 to pi + var lpf1 = lowPassFIRFilter(wc1, N); #ideal low -pass filter + var lpf1_w = lpf1 * hamming(N); + + var wc2 = 2 * pi * fc2 / Fs; + var lpf2 = lowPassFIRFilter(wc2, N); + var lpf2_w = lpf2 * hamming(N); + + # var bpf = lpf2 - lpf; + var bpf_w = sub(lpf2_w,lpf1_w); + var FIRfilterResponseForBpf = FIRFilterResponse(input_signal, bpf_w); + + # Step 2: Artifact Removal (R-peak detection) + var max_signal = max(FIRfilterResponseForBpf); + + var height = 0.3 * max_signal; + + var r_peaks = find_peaks(FIRfilterResponseForBpf, height, distance); + + var len_r_peaks = len(r_peaks); + var last_peaks_index = sub(len_r_peaks, [1]); + var peaks_count = getSingleElemAtIndx(r_peaks, last_peaks_index); + + #### These make error! We need to change from + #rr_intervals = np.diff(peaks) / fs + #avg_hr = 60 / np.mean(rr_intervals) + #### to + #diff_mean = np.mean(np.diff(peaks)) + #avg_hr_fs = 60 * Fs; + #avg_hr = avg_hr_fs/diff_mean; + + var diff_val = diff(r_peaks, peaks_count); + var peaks_count_minus_one = sub(peaks_count, 1); + var diff_mean = mean(diff_val, peaks_count_minus_one); + + var avg_hr = (60 * Fs) / diff_mean; + + print(avg_hr); + + + +} + From 30b67b1a60a50eb313be6a21c21f8df8ea3c4c9b Mon Sep 17 00:00:00 2001 From: "Kuo, Mei-Chun" <94007620+Megan0704-1@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:05:35 -0700 Subject: [PATCH 22/45] Space communication optimization (#28) * optimization for space modulation * remove comments --- .../dsp/SimpleBlocks/include/toy/Ops.td | 1 + .../dsp/SimpleBlocks/mlir/ToyCombine.cpp | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index d6591348235e..4d6217ac6cd3 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -2370,6 +2370,7 @@ def SpaceDemodulateOp : Dsp_Op<"space_demodulate", ]; let hasVerifier = 1; + let hasCanonicalizer = 1; } //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp index ff58c476388d..8fcabe9faea6 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp @@ -690,6 +690,35 @@ struct SimplifyLMSFilterResponsewithGain } }; +struct SimplifySpaceModDemodulate : public mlir::OpRewritePattern { + SimplifySpaceModDemodulate(mlir::MLIRContext *context) : OpRewritePattern(context, 1) {} + + mlir::LogicalResult + matchAndRewrite(SpaceDemodulateOp op, mlir::PatternRewriter &rewriter) const override { + + // a flag checking if the define operation chain of demod op contains mod op + bool opt = false; + SpaceModulateOp prev_mod; + auto iter = op.getOperand(); + while(iter.getDefiningOp()) { + auto pred = iter.getDefiningOp(); + // llvm::errs() << pred->getName().getStringRef() << "\n"; + if(llvm::dyn_cast(*pred)) { + opt = true; + prev_mod = llvm::dyn_cast(*pred); + break; + } + iter = (*pred).getOperand(0); + } + + if(!opt) return failure(); + + auto constVal = prev_mod.getOperand().getDefiningOp(); + rewriter.replaceOp(op, constVal); + return mlir::success(); + } +}; + // =================================== // =================================== // =================================== @@ -792,3 +821,10 @@ void ReshapeOp::getCanonicalizationPatterns(RewritePatternSet &results, FoldConstantReshapeOptPattern>(context); } } + +void SpaceDemodulateOp::getCanonicalizationPatterns(RewritePatternSet &results, + MLIRContext *context) { + if(getEnableCanonicalOpt()) { + results.add(context); + } +} From e1651cc1a8465c712f198d1572bcb68c29c96e0b Mon Sep 17 00:00:00 2001 From: "Kuo, Mei-Chun" <94007620+Megan0704-1@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:08:14 -0700 Subject: [PATCH 23/45] Power op for 1 dimension array (#4) --- .../dsp/SimpleBlocks/include/toy/Ops.td | 28 ++++++++++ .../dsp/SimpleBlocks/include/toy/Parser.h | 2 + .../dsp/SimpleBlocks/mlir/Dialect.cpp | 28 ++++++++++ .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 54 ++++++++++++++++++- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 11 ++++ mlir/test/Examples/DspExample/dsp_pow_op.py | 18 +++++++ 6 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 mlir/test/Examples/DspExample/dsp_pow_op.py diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 4d6217ac6cd3..78aa5a71cb11 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -330,6 +330,33 @@ def BitwiseAndOp : Dsp_Op<"bitwiseand", ]; } +//===----------------------------------------------------------------------===// +// PowerOp +//===----------------------------------------------------------------------===// + +def PowOp : Dsp_Op<"pow", + [Pure, DeclareOpInterfaceMethods]>{ + let summary = "element-wise power operation for tensor"; + let description = [{ + The "pow" operation performs element-wise power for base tensor. + The accepted operand is restrict to a scaler constant. + }]; + + let arguments = (ins F64Tensor:$lhs, F64Tensor:$rhs); + let results = (outs F64Tensor); + + // has custom parser and printer for method + // FIXME: pow op should have custom assembly format + // let hasCustomAssemblyFormat = 1; + + // Allow building a PowOp from two operands. + let builders = [ + OpBuilder<(ins "Value":$lhs, "Value":$rhs)> + ]; + let hasVerifier = 1; + } + + //===----------------------------------------------------------------------===// // PrintOp //===----------------------------------------------------------------------===// @@ -487,6 +514,7 @@ def DelayOp : Dsp_Op<"delay" , let hasCanonicalizer = 1; let hasVerifier = 1; + } diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Parser.h b/mlir/examples/dsp/SimpleBlocks/include/toy/Parser.h index 238f7d441676..a9d673f8f5f0 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Parser.h +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Parser.h @@ -482,6 +482,8 @@ class Parser { return 40; case '/': return 40; + case '^': + return 60; default: return -1; } diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index ec5cc64994b9..724a14bec5cb 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -804,6 +804,34 @@ mlir::LogicalResult MatmulOp::verify() { +//===----------------------------------------------------------------------===// + // PowOp + //===----------------------------------------------------------------------===// + + void PowOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value lhs, mlir::Value rhs) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({lhs, rhs}); + } + +void PowOp::inferShapes() { getResult().setType(getLhs().getType()); } + +mlir::LogicalResult PowOp::verify() { + auto lhsType = llvm::dyn_cast(getLhs().getType()); + auto resultType = llvm::dyn_cast(getType()); + + if(!lhsType || !resultType) return mlir::success(); + + // ensure result shape matches lhs shape + auto resultShape = resultType.getShape(); + if(!std::equal(lhsType.getShape().begin(), lhsType.getShape().end(), + resultShape.rbegin())) { + return emitError() << "expected result shape to be the same as the lhs input operand."; + } + + return mlir::success(); +} + //===----------------------------------------------------------------------===// // zeroCrossCountOp //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 6dfce74c6ec2..3a3b4e16d462 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -7865,6 +7865,7 @@ using MulOpLowering = BinaryOpLowering; using DivOpLowering = BinaryOpLowering; using SinOpLowering = UnaryOpLowering; using CosOpLowering = UnaryOpLowering; + //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: Constant operations //===----------------------------------------------------------------------===// @@ -8864,8 +8865,57 @@ struct SpaceErrCorrectionOpLowering : public ConversionPattern { return mlir::success(); } }; -} // namespace +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: Power operations +//===----------------------------------------------------------------------===// + +struct PowOpLowering : public ConversionPattern { + PowOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::PowOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + + dsp::PowOpAdaptor powerAdaptor(operands); + Value lhs = powerAdaptor.getLhs(); + Value rhs = powerAdaptor.getRhs(); + + auto inputType = llvm::cast(lhs.getType()); + auto resultType = llvm::cast((*op->result_type_begin())); + + // allocate space for result + auto memRefType = convertTensorToMemRef(resultType); + auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); + // affine loops for input + int64_t lb = 0; + int64_t ub = inputType.getShape()[0]; + int64_t step = 1; + + affine::AffineForOp forOp = rewriter.create(loc, lb, ub, step); + auto iv = forOp.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp.getBody()); + + Value loadLHS = rewriter.create(loc, lhs, ValueRange{iv}); + Value loadRHS = rewriter.create(loc, rhs, ValueRange{}); + + Value power = rewriter.create(loc, loadLHS, loadRHS); + + // store result + rewriter.create(loc, power, alloc, ValueRange{iv}); + rewriter.setInsertionPointAfter(forOp); + + // replace op + rewriter.replaceOp(op, alloc); + return success(); + } +}; + +} // namespace //===----------------------------------------------------------------------===// // ToyToAffineLoweringPass //===----------------------------------------------------------------------===// @@ -8923,7 +8973,7 @@ void ToyToAffineLoweringPass::runOnOperation() { DownSamplingOpLowering, UpSamplingOpLowering, LowPassFilter1stOrderOpLowering, HighPassFilterOpLowering, FFT1DOpLowering, IFFT1DOpLowering, HammingWindowOpLowering, DCTOpLowering, - filterOpLowering, DivOpLowering, BitwiseAndOpLowering, + filterOpLowering, DivOpLowering, BitwiseAndOpLowering, PowOpLowering, zeroCrossCountOpLowering, SumOpLowering, SinOpLowering, CosOpLowering, SquareOpLowering, FFT1DRealOpLowering, FFT1DImgOpLowering, SincOpLowering, GetElemAtIndxOpLowering, SetElemAtIndxOpLowering, diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index dfa56759e902..83693be838a7 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -218,6 +218,8 @@ class MLIRGenImpl { return builder.create(location, lhs, rhs); case '-': return builder.create(location, lhs, rhs); + case '^': + return builder.create(location, lhs, rhs); } emitError(location, "invalid binary operator '") << binop.getOp() << "'"; @@ -379,6 +381,15 @@ class MLIRGenImpl { } return builder.create(location, operands[0], operands[1]); } + if(callee == "pow"){ + if(call.getArgs().size() != 2){ + emitError(location, "MLIR codegen encountered an error: dsp.pow " + "accepts only 2 arguments"); + return nullptr; + } + return builder.create(location, operands[0], operands[1]); + } + // Modulo Op if (callee == "modulo") { diff --git a/mlir/test/Examples/DspExample/dsp_pow_op.py b/mlir/test/Examples/DspExample/dsp_pow_op.py new file mode 100644 index 000000000000..ff9b156ba492 --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_pow_op.py @@ -0,0 +1,18 @@ +# RUN: dsp1 %s -emit=mlir 2>&1 | FileCheck %s + +def main() { + var a = [4,20]; + var b = 4; + #var c = pow(a, b); + var c = a^b; + print(c); +} +# /home/local/ASUAD/apkhedka/ForLLVM/build/bin/dsp1 /home/local/ASUAD/apkhedka/ForLLVM/mlir/test/Examples/DspExample/dsp_pow_op.py -emit=mlir + +# CHECK-LABEL: dsp.func @main() { +# CHECK-NEXT: %[[VAL_0:.*]] = dsp.constant dense<{{\[\[}}[1.000000e+01, 2.000000e+01], [3.000000e+01, 0.000000e+00]]]> : tensor<1x2x2xf64> +# CHECK-NEXT: %[[VAL_1:.*]] = dsp.constant dense<[1.000000e+01]> : tensor<1xf64> +# CHECK-NEXT: %[[VAL_2:.*]] = "dsp.sub"(%[[VAL_0]], %[[VAL_1]]) : (tensor<3xf64>, tensor<3xf64>) -> tensor<*xf64> +# CHECK-NEXT: dsp.print %[[VAL_2]] : tensor<*xf64> +# CHECK-NEXT: dsp.return +# CHECK-NEXT: } From f628f58a22e8613f25876e7ccf3fa96ad03af85a Mon Sep 17 00:00:00 2001 From: "Kuo, Mei-Chun" <94007620+Megan0704-1@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:22:37 -0700 Subject: [PATCH 24/45] Beam form application v1 (#29) * add beamform op * format lowertoaffine --------- Co-authored-by: Atharva Khedkar --- .../BenchmarkTest/DSP-DSL/beamForm-v0.py | 11 + .../BenchmarkTest/DSP-DSL/beamForm.py | 38 ++- .../dsp/SimpleBlocks/include/toy/Ops.td | 35 +++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 96 +++++--- .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 223 +++++++++++++----- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 25 ++ .../Examples/DspExample/dsp_abs_argmax.py | 14 ++ 7 files changed, 347 insertions(+), 95 deletions(-) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/beamForm-v0.py create mode 100644 mlir/test/Examples/DspExample/dsp_abs_argmax.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/beamForm-v0.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/beamForm-v0.py new file mode 100644 index 000000000000..b2981798de01 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/beamForm-v0.py @@ -0,0 +1,11 @@ +def main() { + + var num_antennas = 4; + var num_samples = 100; + var frequency = 5; + var time = getRangeOfVector(0, 100, 1); + var weights = [1,2,3,4]; + + var signal = beam_form(num_antennas, frequency, time, weights); + print(signal); +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/beamForm.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/beamForm.py index 0cd9c02b1f83..f1e51830182a 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/beamForm.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/beamForm.py @@ -1,11 +1,31 @@ def main() { - var num_antennas = 4; - var num_samples = 100; - var frequqncy = 5; - var time = getRangeOfVector(0, 100, 1); - var weights = [1,2,3,4]; - - var signal = beam_form(num_antennas, frequqncy, time, weights); - print(signal); -} \ No newline at end of file + var antennas = 4; + var input_fc = 5; + var N = 101; + var time = getRangeOfVector(0, 100, 0.01); + var weights = getRangeOfVector(-90, 180, 1); + + var signal = beam_form(antennas, input_fc, time, weights); + var b1 = abs(signal); + var power_profile = b1 * b1; + var power_angle_max_idx = argmax(power_profile, 0); + var power_angle_max_ele = argmax(power_profile,0); + + var pi = 3.1415926; + var fc1 = 1000; + var fc2 = 7500; + var Fs = 8000; + + var wc1 = 2*pi*fc1 / Fs; + var filter1 = lowPassFIRFilter(wc1, N); + var filter_hamming_1 = filter1 * hamming(N); + var wc2 = 2*pi*fc2 / Fs; + var filter2 = lowPassFIRFilter(wc2, N); + var filter_hamming_2 = filter2 * hamming(N); + + var bpf = sub(filter_hamming_2, filter_hamming_1); + var firFilterResponse = FIRFilterResponse(power_profile, bpf); + print(firFilterResponse); +} + diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 78aa5a71cb11..34e78a6ba417 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -2513,8 +2513,43 @@ def DiffOp : Dsp_Op<"diff", [Pure , DeclareOpInterfaceMethods]> { + let summary = "np.abs -> calculate the absolute value element-wise"; + let description = [{ + This operation calculates the absolute value element-wise. + }]; + + let arguments = (ins F64Tensor:$input); + let results = (outs F64Tensor:$output); + + let builders = [ + OpBuilder<(ins "Value":$input)> + ]; + } +//===----------------------------------------------------------------------===// +// ArgMaxOp +//===----------------------------------------------------------------------===// + +def ArgMaxOp : Dsp_Op<"argmax", [Pure , DeclareOpInterfaceMethods]> { + let summary = "np.argmax -> find the indices of the maximum values along a specifies axis in an array."; + let description = [{ + This operation find the indices of the maximum values along a specifies axis in an array. + }]; + + let arguments = (ins F64Tensor:$input, I64Attr:$axis); + let results = (outs F64Tensor:$output); + + let builders = [ + OpBuilder<(ins "Value":$input, "int64_t":$axis)> + ]; + } + #endif // TOY_OPS diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index 724a14bec5cb..22714eeae0db 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -799,10 +799,50 @@ mlir::LogicalResult MatmulOp::verify() { } +//===----------------------------------------------------------------------===// + // AbsOp + //===----------------------------------------------------------------------===// + + void AbsOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value input) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({input}); + } + void AbsOp::inferShapes() { getResult().setType(getInput().getType()); } +//===----------------------------------------------------------------------===// + // ArgMaxOp + //===----------------------------------------------------------------------===// + void ArgMaxOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, + mlir::Value input, int64_t axis) { + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addAttribute("axis", builder.getI64IntegerAttr(axis)); + state.addOperands({input}); + } + + void ArgMaxOp::inferShapes() { + + auto inputType = getInput().getType(); + auto inputRank = inputType.getRank(); + auto inputShape = inputType.getShape(); + + if(inputRank == 1) { + vector outputShape(1, 1); + auto outputType = mlir::RankedTensorType::get(outputShape, inputType.getElementType()); + getResult().setType(outputType); + return; + } + + int64_t axis = getAxis(); + int64_t dim = axis==1 ? 0 : 1; + + auto outputType = mlir::RankedTensorType::get(inputShape[dim], inputType.getElementType()); + + getResult().setType(outputType); +} //===----------------------------------------------------------------------===// // PowOp @@ -3032,20 +3072,20 @@ void QamModulateRealOp::inferShapes() { mlir::LogicalResult QamModulateRealOp::verify() { - auto signalType = llvm::dyn_cast(getSignal().getType()); - - if(!signalType) { - llvm::errs() << "expect a ranked tensor for signal input, get " << getSignal(); - return mlir::failure(); - } - - auto signalRank = signalType.getRank(); - - if(signalRank != 1 ) { - llvm::errs() << "expect 1 dimensional signal, get " << signalRank; - return mlir::failure(); - } - + //auto signalType = llvm::dyn_cast(getSignal().getType()); +// + //if(!signalType) { + //llvm::errs() << "expect a ranked tensor for signal input, get " << getSignal(); + //return mlir::failure(); + //} +// + //auto signalRank = signalType.getRank(); +// + //if(signalRank != 1 ) { + //llvm::errs() << "expect 1 dimensional signal, get " << signalRank; + //return mlir::failure(); + //} + // return mlir::success(); } @@ -3074,20 +3114,20 @@ void QamModulateImgOp::inferShapes() { mlir::LogicalResult QamModulateImgOp::verify() { - auto signalType = llvm::dyn_cast(getSignal().getType()); - - if(!signalType) { - llvm::errs() << "expect a ranked tensor for signal input, get " << getSignal(); - return mlir::failure(); - } - - auto signalRank = signalType.getRank(); - - if(signalRank != 1 ) { - llvm::errs() << "expect 1 dimensional signal, get " << signalRank; - return mlir::failure(); - } - + // auto signalType = llvm::dyn_cast(getSignal().getType()); +// + // if(!signalType) { + // llvm::errs() << "expect a ranked tensor for signal input, get " << getSignal(); + // return mlir::failure(); + // } +// + // auto signalRank = signalType.getRank(); +// + // if(signalRank != 1 ) { + // llvm::errs() << "expect 1 dimensional signal, get " << signalRank; + // return mlir::failure(); + // } + // return mlir::success(); } diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 3a3b4e16d462..dad67948a22c 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -7863,6 +7863,7 @@ using ModuloOpLowering = BinaryOpLowering; using SubOpLowering = BinaryOpLowering; using MulOpLowering = BinaryOpLowering; using DivOpLowering = BinaryOpLowering; +using AbsOpLowering = UnaryOpLowering; using SinOpLowering = UnaryOpLowering; using CosOpLowering = UnaryOpLowering; @@ -8586,19 +8587,23 @@ struct BeamFormOpLowering : public ConversionPattern { // generated input map AffineMap genInputMap = - AffineMap::get(2 /* dim */, 0 /* sym */, ArrayRef{d0, d1}, + AffineMap::get(2 /* dim */, 0 /* sym */, ArrayRef{d1, d0}, rewriter.getContext()); // time affine map AffineMap timeMap = AffineMap::get(2 /* dim */, 0 /* sym */, ArrayRef{d1}, rewriter.getContext()); - // output map - AffineMap outputMap = - AffineMap::get(2, 0, ArrayRef{d0}, rewriter.getContext()); + // // output map + // AffineMap outputMap = + // AffineMap::get(2, 0, ArrayRef{d0}, rewriter.getContext()); auto pi = rewriter.create( loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(3.1415926)); + auto zero = rewriter.create(loc, rewriter.getF64Type(), + rewriter.getF64FloatAttr(0)); + auto one = rewriter.create(loc, rewriter.getF64Type(), + rewriter.getF64FloatAttr(1)); auto two = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(2)); auto four = rewriter.create(loc, rewriter.getF64Type(), @@ -8606,20 +8611,18 @@ struct BeamFormOpLowering : public ConversionPattern { auto two_pi = rewriter.create(loc, pi, two); // 2 * pi auto freq_val = rewriter.create( loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(frequency)); - auto phase_var = rewriter.create(loc, two_pi, freq_val); + auto phase_var = + rewriter.create(loc, two_pi, freq_val); // 2*pi*freq // for loop from 0 to phase int64_t lb = 0, ub = antennas, step = 1; affine::AffineForOp forOpI = - rewriter.create(loc, lb, ub, step); + rewriter.create(loc, lb, ub, step, ValueRange{zero}); auto ivI = forOpI.getInductionVar(); // i : phase rewriter.setInsertionPointToStart(forOpI.getBody()); // get the induction var to phase variable - auto intType = rewriter.getI64Type(); - auto intI = rewriter.create(loc, intType, ivI); - auto floatType = rewriter.getF64Type(); - auto floatI = rewriter.create(loc, floatType, intI); + auto floatI = forOpI.getBody()->getArgument(1); auto iter_tmp = rewriter.create(loc, floatI, pi); // i * pi auto iter_args = @@ -8641,18 +8644,22 @@ struct BeamFormOpLowering : public ConversionPattern { rewriter.create(loc, result, allocSignal, ValueRange{ivI, ivJ}); - rewriter.setInsertionPointAfter(forOpJ); - rewriter.setInsertionPointAfter(forOpI); + rewriter.setInsertionPointAfter(forOpJ); // end for loop: j - ub = antennas; + auto increFloatI = rewriter.create(loc, floatI, one); + rewriter.create(loc, ValueRange{increFloatI}); + + rewriter.setInsertionPointAfter(forOpI); // end for loop: i + + ub = timeDim; affine::AffineForOp forOpIOut = rewriter.create(loc, lb, ub, step); auto ivIoutput = forOpIOut.getInductionVar(); rewriter.setInsertionPointToStart(forOpIOut.getBody()); - ub = timeDim; + ub = antennas; affine::AffineForOp forOpJOut = - rewriter.create(loc, lb, ub, step); + rewriter.create(loc, lb, ub, step, ValueRange{zero}); auto ivJoutput = forOpJOut.getInductionVar(); rewriter.setInsertionPointToStart(forOpJOut.getBody()); @@ -8660,17 +8667,16 @@ struct BeamFormOpLowering : public ConversionPattern { auto signalInput = rewriter.create( loc, allocSignal, genInputMap, ValueRange{ivIoutput, ivJoutput}); auto weight = rewriter.create( - loc, weights, outputMap, ValueRange{ivIoutput, ivJoutput}); + loc, weights, timeMap, ValueRange{ivIoutput, ivJoutput}); auto intermediateVal = rewriter.create(loc, signalInput, weight); - // load from output - auto outputVal = - rewriter.create(loc, alloc, ValueRange{ivJoutput}); - auto beamOut = - rewriter.create(loc, intermediateVal, outputVal); + // iterargs + auto sumVal = forOpJOut.getBody()->getArgument(1); + auto beamOut = rewriter.create(loc, intermediateVal, sumVal); - rewriter.create(loc, beamOut, alloc, ValueRange{ivJoutput}); + rewriter.create(loc, beamOut, alloc, ValueRange{ivIoutput}); + rewriter.create(loc, ValueRange{beamOut}); rewriter.setInsertionPointAfter(forOpJOut); rewriter.setInsertionPointAfter(forOpIOut); @@ -8865,57 +8871,157 @@ struct SpaceErrCorrectionOpLowering : public ConversionPattern { return mlir::success(); } }; + +struct ArgMaxOpLowering : public ConversionPattern { + ArgMaxOpLowering(MLIRContext *context) + : ConversionPattern(dsp::ArgMaxOp::getOperationName(), 1, context) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + auto zeroVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + auto oneVal = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(1)); + + // argmax adaptor + ArgMaxOpAdaptor adaptor(operands); + auto input = adaptor.getInput(); + auto inputType = + llvm::dyn_cast(op->getOperand(0).getType()); + + // get operation + auto argmaxOp = llvm::dyn_cast(op); + + // get attribute + int64_t axis = argmaxOp.getAxis(); + + // output allocation + auto output = llvm::dyn_cast((*op->result_type_begin())); + auto outputMemRef = convertTensorToMemRef(output); + auto alloc = insertAllocAndDealloc(outputMemRef, loc, + rewriter); // stroing max ele index + + auto allocEle = + insertAllocAndDealloc(outputMemRef, loc, rewriter); // stroing max ele + + auto outputShape = output.getShape(); + auto outputSizeOp = rewriter.create( + loc, rewriter.getF64Type(), + rewriter.getF64FloatAttr(outputShape.size())); + + auto sizeSwitch = rewriter.create( + loc, arith::CmpFPredicate::OEQ, outputSizeOp, + oneVal); // if outputsize > 1 + AffineExpr d0; + bindDims(rewriter.getContext(), d0); + AffineMap zeroIdx = AffineMap::get(1, 0, ArrayRef{d0 - d0}, + rewriter.getContext()); + + auto ifOp = rewriter.create( + loc, sizeSwitch, + true); // FIXME: else condition for 2 dimensional tensor input + rewriter.setInsertionPointToStart(ifOp.thenBlock()); + // output size == 1 + /* -> one loop through tensor, recording max val and its index + */ + Value iv0 = rewriter.create(loc, 0); + rewriter.create(loc, zeroVal, allocEle, ValueRange{iv0}); + + auto zero = rewriter.create(loc, rewriter.getF64Type(), + rewriter.getF64FloatAttr(0)); + auto one = rewriter.create(loc, rewriter.getF64Type(), + rewriter.getF64FloatAttr(1)); + + int lb = 0, ub = inputType.getShape()[0], step = 1; + auto forOp = + rewriter.create(loc, lb, ub, step, ValueRange{zero}); + auto ivI = forOp.getInductionVar(); + rewriter.setInsertionPointToStart(forOp.getBody()); + + auto floatI = forOp.getBody()->getArgument(1); + + auto curMax = + rewriter.create(loc, allocEle, zeroIdx, ValueRange{ivI}); + auto curMaxIdx = + rewriter.create(loc, alloc, zeroIdx, ValueRange{ivI}); + auto curEle = rewriter.create(loc, input, ivI); + auto cmpOp = rewriter.create(loc, arith::CmpFPredicate::OGT, + curEle, curMax); + // if ele > max: update val + auto maxOp = rewriter.create(loc, cmpOp, curEle, curMax); + + // store the idx based on cmp output + auto idxOp = + rewriter.create(loc, cmpOp, floatI, curMaxIdx); + + rewriter.create(loc, maxOp, allocEle, zeroIdx, + ValueRange{ivI}); + rewriter.create(loc, idxOp, alloc, zeroIdx, ValueRange{ivI}); + + auto increFloatI = rewriter.create(loc, floatI, one); + rewriter.create(loc, ValueRange{increFloatI}); + + rewriter.setInsertionPointAfter(forOp); + rewriter.setInsertionPointAfter(ifOp); + + rewriter.replaceOp(op, alloc); + return mlir::success(); + } +}; //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: Power operations //===----------------------------------------------------------------------===// struct PowOpLowering : public ConversionPattern { - PowOpLowering(MLIRContext *ctx) - : ConversionPattern(dsp::PowOp::getOperationName(), 1, ctx) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const final { - auto loc = op->getLoc(); + PowOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::PowOp::getOperationName(), 1, ctx) {} + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); - dsp::PowOpAdaptor powerAdaptor(operands); - Value lhs = powerAdaptor.getLhs(); - Value rhs = powerAdaptor.getRhs(); + dsp::PowOpAdaptor powerAdaptor(operands); + Value lhs = powerAdaptor.getLhs(); + Value rhs = powerAdaptor.getRhs(); - auto inputType = llvm::cast(lhs.getType()); - auto resultType = llvm::cast((*op->result_type_begin())); + auto inputType = llvm::cast(lhs.getType()); + auto resultType = llvm::cast((*op->result_type_begin())); - // allocate space for result - auto memRefType = convertTensorToMemRef(resultType); - auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); + // allocate space for result + auto memRefType = convertTensorToMemRef(resultType); + auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); - // affine loops for input - int64_t lb = 0; - int64_t ub = inputType.getShape()[0]; - int64_t step = 1; + // affine loops for input + int64_t lb = 0; + int64_t ub = inputType.getShape()[0]; + int64_t step = 1; - affine::AffineForOp forOp = rewriter.create(loc, lb, ub, step); - auto iv = forOp.getInductionVar(); + affine::AffineForOp forOp = rewriter.create(loc, lb, ub, step); + auto iv = forOp.getInductionVar(); - rewriter.setInsertionPointToStart(forOp.getBody()); + rewriter.setInsertionPointToStart(forOp.getBody()); - Value loadLHS = rewriter.create(loc, lhs, ValueRange{iv}); - Value loadRHS = rewriter.create(loc, rhs, ValueRange{}); + Value loadLHS = rewriter.create(loc, lhs, ValueRange{iv}); + Value loadRHS = rewriter.create(loc, rhs, ValueRange{}); - Value power = rewriter.create(loc, loadLHS, loadRHS); + Value power = rewriter.create(loc, loadLHS, loadRHS); - // store result - rewriter.create(loc, power, alloc, ValueRange{iv}); - rewriter.setInsertionPointAfter(forOp); + // store result + rewriter.create(loc, power, alloc, ValueRange{iv}); + rewriter.setInsertionPointAfter(forOp); - // replace op - rewriter.replaceOp(op, alloc); - return success(); + // replace op + rewriter.replaceOp(op, alloc); + return success(); } }; } // namespace + //===----------------------------------------------------------------------===// // ToyToAffineLoweringPass //===----------------------------------------------------------------------===// @@ -8987,11 +9093,12 @@ void ToyToAffineLoweringPass::runOnOperation() { FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering, ShiftRightOpLowering, MatmulOpLowering, ThresholdUpOpLowering, QamModulateRealOpLowering, - QamModulateImgOpLowering, QamDemodulateOpLowering, FindPeaksOpLowering, - BeamFormOpLowering, SpaceModulateOpLowering, SpaceDemodulateOpLowering, - SpaceErrCorrectionOpLowering, FindPeaksOpLowering, MaxOpLowering, - MeanOpLowering, DiffOpLowering, GetSingleElemAtIdxOpLowering, Diff2MeanOptimizedOpLowering>( - &getContext()); + QamModulateImgOpLowering, QamDemodulateOpLowering, BeamFormOpLowering, + SpaceModulateOpLowering, SpaceDemodulateOpLowering, + SpaceErrCorrectionOpLowering, MedianFilterOpLowering, FindPeaksOpLowering, + MaxOpLowering, MeanOpLowering, DiffOpLowering, AbsOpLowering, + ArgMaxOpLowering, GetSingleElemAtIdxOpLowering, + Diff2MeanOptimizedOpLowering>(&getContext()); // With the target and rewrite patterns defined, we can now attempt the // conversion. The conversion will signal failure if any of our `illegal` diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index 83693be838a7..881880a1f4e1 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -462,6 +462,31 @@ class MLIRGenImpl { } return builder.create(location, operands[0], operands[1]); } + + // Abs Op + if(callee == "abs") { + if (call.getArgs().size() != 1) { + emitError(location, "MLIR codegen encountered an error: dsp.abs " + "accepts only 1 arguments: input tensor."); + return nullptr; + } + return builder.create(location, operands[0]); + } + + // ArgMax Op + if(callee == "argmax") { + if (call.getArgs().size() != 2) { + emitError(location, "MLIR codegen encountered an error: dsp.argmax " + "accepts only 2 arguments: input tensor, axis."); + return nullptr; + } + + auto axisOp = operands[1].getDefiningOp(); + auto axisVal = axisOp.getValue().getValues(); + double axis = axisVal[0].getValueAsDouble(); + + return builder.create(location, operands[0], axis); + } // Shift right Op if (callee == "shiftRight") { diff --git a/mlir/test/Examples/DspExample/dsp_abs_argmax.py b/mlir/test/Examples/DspExample/dsp_abs_argmax.py new file mode 100644 index 000000000000..bd8673fd1aed --- /dev/null +++ b/mlir/test/Examples/DspExample/dsp_abs_argmax.py @@ -0,0 +1,14 @@ +def main() { + # var time = [0.0, -0.25, 0.5, -0.75, 1.0]; + var time = getRangeOfVector(0, 100, 0.01); + var antennas = 4; + var freq = 5; + var weights = [1, 7, 6, -7]; + + var signal = beam_form(antennas, freq, time, weights); + var abs_signal = abs(signal); + var power_abs_signal= abs_signal * abs_signal; + var max_power_angle_idx = argmax(signal, 0); + + print(max_power_angle_idx); +} From dab151bdf2be002c12b6cf18b5d0fd7d30ed7459 Mon Sep 17 00:00:00 2001 From: Atharva Khedkar <55466743+AtharvaKhedkar@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:33:06 -0700 Subject: [PATCH 25/45] Add benchmark applications (#27) * Add signalSmoothing app * add biomedsignal processing * fix digital beam form bug * added scffor instead affine * Added fusing optimization for lms and findPeaks * fix fft1dsymm lowering * update applications space, modulate and beamform --------- Co-authored-by: megakuo Co-authored-by: hwisooso --- .../BenchmarkTest/CCode/ResultScript.py | 4 +- .../CCode/biomedicalSignalProcessing.c | 214 +++++++ .../BenchmarkTest/CCode/digitalModulation.c | 77 +++ .../BenchmarkTest/CCode/periodogram2Conv.c | 5 +- .../BenchmarkTest/CCode/signalSmoothing.c | 201 +++++++ .../BenchmarkTest/CCode/signalsmoothing.c | 96 ---- .../BenchmarkTest/CCode/spaceCommunication.c | 271 +++++---- .../BenchmarkTest/CCode/targetDetection.c | 142 +++++ .../BenchmarkTest/DSP-DSL/ResultScript.py | 8 +- .../DSP-DSL/biomedicalSignalProcessing.py | 56 ++ .../DSP-DSL/digitalModulation.py | 20 + .../DSP-DSL/digital_modulation.py | 10 - .../DSP-DSL/lowPassFIRFilterDesign.py | 2 +- .../DSP-DSL/periodogram2Conv1.py | 23 +- .../BenchmarkTest/DSP-DSL/signalSmoothing.py | 20 + .../BenchmarkTest/DSP-DSL/signalsmoothing.py | 6 - .../DSP-DSL/spaceCommunication.py | 15 +- .../BenchmarkTest/DSP-DSL/targetDetection.py | 27 + .../DSP-DSL/vibrationAnalysis.py | 8 +- .../BenchmarkTest/Matlab/ResultScript.py | 4 +- .../Matlab/biomedicalSignalProcessing.m | 60 ++ .../BenchmarkTest/Matlab/signalSmoothing.m | 30 + .../BenchmarkTest/Matlab/signalsmoothing.m | 40 -- .../BenchmarkTest/Matlab/targetDetection.m | 51 ++ .../dsp/SimpleBlocks/include/toy/Ops.td | 19 + .../dsp/SimpleBlocks/mlir/Dialect.cpp | 49 ++ .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 537 ++++++++++++++++-- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 13 + .../dsp/SimpleBlocks/mlir/ToyCombine.cpp | 135 +++-- 29 files changed, 1754 insertions(+), 389 deletions(-) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/biomedicalSignalProcessing.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/digitalModulation.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/signalSmoothing.c delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/signalsmoothing.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/targetDetection.c create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/biomedicalSignalProcessing.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/digitalModulation.py delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/digital_modulation.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/signalSmoothing.py delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/signalsmoothing.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/targetDetection.py create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/biomedicalSignalProcessing.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/signalSmoothing.m delete mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/signalsmoothing.m create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/targetDetection.m diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/ResultScript.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/ResultScript.py index fdd325ebecef..9ae504810d8e 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/ResultScript.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/ResultScript.py @@ -15,8 +15,8 @@ # Run the respective commands on the file # Path to the input file -# Apps = "lowPassFIRFilterDesign.c", "noisecancelling.c" , "echocancelling.c", "hearingAid.c", "audioEqualizer.c", "vibrationAnalysis.c", "underWaterCommunication.c", "voiceActivityDetection.c" -input_file_path = "vibrationAnalysis.c" +# Apps = "lowPassFIRFilterDesign.c", "noisecancelling.c" , "echocancelling.c", "hearingAid.c", "audioEqualizer.c", "vibrationAnalysis.c", "underWaterCommunication.c", "voiceActivityDetection.c", "signalSmoothing", "targetDetection", "biomedicalSignalProcessing", "periodogram2Conv", "spaceCommunication" +input_file_path = "spaceCommunication.c" BasePathForLLVM = "/home/local/ASURITE/apkhedka/ForLLVM/" OutputScriptPath = "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/" # OutputPath = BasePathForLLVM + "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/Results/TryResultScript/Output/" diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/biomedicalSignalProcessing.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/biomedicalSignalProcessing.c new file mode 100644 index 000000000000..60d67b6bcdd0 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/biomedicalSignalProcessing.c @@ -0,0 +1,214 @@ +#include +#include +#include + +#define PI 3.14159265359 +#define INPUT_LENGTH 100000000 +#define MAX_PEAKS 1000 + +// Function declarations +void getRangeOfVector(double* vector, double start, int length, double increment); +void gain(double* output, double* input, double multiplier, int length); +void sine(double* output, double* input, int length); +void add_signals(double* output, double* input1, double* input2, int length); +void sub_signals(double* output, double* input1, double* input2, int length); +void multiply_signals(double* output, double* input1, double* input2, int length); +void lowPassFIRFilter(double* lpf, double wc, int N); +void hamming(double* hamming, int N); +void FIRFilterResponse(double* output, double* input, double* filter, int input_length, int filter_length); +double max_signal(double* signal, int length); +int find_peaks(double* signal, int length, double threshold, int minDistance, int* peaks); +void diff(double* output, int* input, int length); +double mean(double* input, int length); + +int main() { + double fc1 = 1000; + double fc2 = 7500; + double Fs = 8000; + int N = 101; + int distance = 950; + + double* input = (double*)malloc(INPUT_LENGTH * sizeof(double)); + getRangeOfVector(input, 0, INPUT_LENGTH, 0.000125); + + double f_sig = 500; + double getMultiplier = 2 * PI * f_sig; + double* getSinDuration = (double*)malloc(INPUT_LENGTH * sizeof(double)); + gain(getSinDuration, input, getMultiplier, INPUT_LENGTH); + + double* clean_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); + sine(clean_sig, getSinDuration, INPUT_LENGTH); + + double f_noise = 3000; + double* getNoiseSinDuration = (double*)malloc(INPUT_LENGTH * sizeof(double)); + gain(getNoiseSinDuration, input, 2 * PI * f_noise, INPUT_LENGTH); + + double* noise = (double*)malloc(INPUT_LENGTH * sizeof(double)); + sine(noise, getNoiseSinDuration, INPUT_LENGTH); + + double* noise1 = (double*)malloc(INPUT_LENGTH * sizeof(double)); + gain(noise1, noise, 0.5, INPUT_LENGTH); + + double* noisy_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); + add_signals(noisy_sig, clean_sig, noise1, INPUT_LENGTH); + + // Step 1: FIR Bandpass Filter + double wc1 = 2 * PI * fc1 / Fs; + double* lpf1 = (double*)malloc(N * sizeof(double)); + lowPassFIRFilter(lpf1, wc1, N); + + double* hamming_window = (double*)malloc(N * sizeof(double)); + hamming(hamming_window, N); + + double* lpf1_w = (double*)malloc(N * sizeof(double)); + multiply_signals(lpf1_w, lpf1, hamming_window, N); + + double wc2 = 2 * PI * fc2 / Fs; + double* lpf2 = (double*)malloc(N * sizeof(double)); + lowPassFIRFilter(lpf2, wc2, N); + + double* lpf2_w = (double*)malloc(N * sizeof(double)); + multiply_signals(lpf2_w, lpf2, hamming_window, N); + + double* bpf_w = (double*)malloc(N * sizeof(double)); + sub_signals(bpf_w, lpf2_w, lpf1_w, N); + + double* FIRfilterResponseForBpf = (double*)malloc(INPUT_LENGTH * sizeof(double)); + FIRFilterResponse(FIRfilterResponseForBpf, noisy_sig, bpf_w, INPUT_LENGTH, N); + + // Step 2: Artifact Removal (R-peak detection) + double max_val = max_signal(FIRfilterResponseForBpf, INPUT_LENGTH); + double height = 0.3 * max_val; + + int* r_peaks = (int*)malloc(MAX_PEAKS * sizeof(int)); + int peaks_count = find_peaks(FIRfilterResponseForBpf, INPUT_LENGTH, height, distance, r_peaks); + + double* diff_val = (double*)malloc((peaks_count - 1) * sizeof(double)); + diff(diff_val, r_peaks, peaks_count); + + double diff_mean = mean(diff_val, peaks_count - 1); + + double avg_hr = (60 * Fs) / diff_mean; + + printf("%f\n", avg_hr); + + // Free allocated memory + free(input); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + free(noise1); + free(noisy_sig); + free(lpf1); + free(hamming_window); + free(lpf1_w); + free(lpf2); + free(lpf2_w); + free(bpf_w); + free(FIRfilterResponseForBpf); + free(r_peaks); + free(diff_val); + + return 0; +} + +void getRangeOfVector(double* vector, double start, int length, double increment) { + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; + } +} + +void gain(double* output, double* input, double multiplier, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * multiplier; + } +} + +void sine(double* output, double* input, int length) { + for (int i = 0; i < length; i++) { + output[i] = sin(input[i]); + } +} + +void add_signals(double* output, double* input1, double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] + input2[i]; + } +} + +void sub_signals(double* output, double* input1, double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] - input2[i]; + } +} + +void multiply_signals(double* output, double* input1, double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] * input2[i]; + } +} + +void hamming(double* hamming, int N) { + for (int n = 0; n < N; n++) { + hamming[n] = 0.54 - 0.46 * cos(2 * PI * n / (N - 1)); + } +} + +void FIRFilterResponse(double* output, double* input, double* filter, int input_length, int filter_length) { + int i, j; + for (i = 0; i < input_length; i++) { + output[i] = 0; + for (j = 0; j < filter_length; j++) { + if (i - j >= 0) { + output[i] += input[i - j] * filter[j]; + } + } + } +} + +void lowPassFIRFilter(double* lpf, double wc, int N) { + int mid = (N - 1) / 2; + for (int n = 0; n < N; n++) { + if (n == mid) { + lpf[n] = wc / PI; + } else { + lpf[n] = (wc / PI) * sin(wc * (n - mid)) / (wc * (n - mid)); + } + } +} + +int find_peaks(double* signal, int length, double threshold, int minDistance, int* peaks) { + int num_peaks = 0; + for (int i = 1; i < length - 1 && num_peaks < MAX_PEAKS; i++) { + if (signal[i] > threshold && signal[i] > signal[i-1] && signal[i] > signal[i+1]) { + peaks[num_peaks++] = i; + i += minDistance; + } + } + return num_peaks; +} + +double max_signal(double* signal, int length) { + double max = signal[0]; + for (int i = 1; i < length; i++) { + if (signal[i] > max) { + max = signal[i]; + } + } + return max; +} + +void diff(double* output, int* input, int length) { + for (int i = 0; i < length - 1; i++) { + output[i] = (double)(input[i+1] - input[i]); + } +} + +double mean(double* input, int length) { + double sum = 0; + for (int i = 0; i < length; i++) { + sum += input[i]; + } + return sum / length; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/digitalModulation.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/digitalModulation.c new file mode 100644 index 000000000000..01bc83c08fa3 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/digitalModulation.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include + +#define INPUT_LENGTH 100000000 + +void generate_input(int *data) { + for(int i=0; i +#include +#include + +#define PI 3.14159265359 +#define INPUT_LENGTH 20000 +#define SAMPLE_RATE 8000 +#define TIME_INCREMENT 0.000125 +#define WINDOW_SIZE 3 + +// Function declarations +void getRangeOfVector(double* vector, double start, int length, double increment); +void gain(double* output, double* input, double multiplier, int length); +void sine(double* output, double* input, int length); +void sliding_median_filter(double* input, double* output, int length); +void sliding_avg_filter(double* input, double* output, int length); +double min_of_three(double a, double b, double c); +double max_of_three(double a, double b, double c); + +int main() { + double fs = SAMPLE_RATE; + double* input = (double*)malloc(INPUT_LENGTH * sizeof(double)); + if (input == NULL) { + fprintf(stderr, "Memory allocation failed for input\n"); + return 1; + } + + double f_sig = 500; + double getMultiplier = 2 * PI * f_sig; + + getRangeOfVector(input, 0, INPUT_LENGTH, TIME_INCREMENT); + + double* getSinDuration = (double*)malloc(INPUT_LENGTH * sizeof(double)); + if (getSinDuration == NULL) { + fprintf(stderr, "Memory allocation failed for getSinDuration\n"); + free(input); + return 1; + } + gain(getSinDuration, input, getMultiplier, INPUT_LENGTH); + + double* clean_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); + if (clean_sig == NULL) { + fprintf(stderr, "Memory allocation failed for clean_sig\n"); + free(input); + free(getSinDuration); + return 1; + } + sine(clean_sig, getSinDuration, INPUT_LENGTH); + + double f_noise = 3000; + double* getNoiseSinDuration = (double*)malloc(INPUT_LENGTH * sizeof(double)); + if (getNoiseSinDuration == NULL) { + fprintf(stderr, "Memory allocation failed for getNoiseSinDuration\n"); + free(input); + free(getSinDuration); + free(clean_sig); + return 1; + } + gain(getNoiseSinDuration, input, 2 * PI * f_noise, INPUT_LENGTH); + + double* noise = (double*)malloc(INPUT_LENGTH * sizeof(double)); + if (noise == NULL) { + fprintf(stderr, "Memory allocation failed for noise\n"); + free(input); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + return 1; + } + sine(noise, getNoiseSinDuration, INPUT_LENGTH); + + double* noise1 = (double*)malloc(INPUT_LENGTH * sizeof(double)); + if (noise1 == NULL) { + fprintf(stderr, "Memory allocation failed for noise1\n"); + free(input); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + return 1; + } + gain(noise1, noise, 0.5, INPUT_LENGTH); + + double* noisy_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); + if (noisy_sig == NULL) { + fprintf(stderr, "Memory allocation failed for noisy_sig\n"); + free(input); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + free(noise1); + return 1; + } + for (int i = 0; i < INPUT_LENGTH; i++) { + noisy_sig[i] = clean_sig[i] + noise1[i]; + } + + double* median = (double*)malloc((INPUT_LENGTH - WINDOW_SIZE + 1) * sizeof(double)); + if (median == NULL) { + fprintf(stderr, "Memory allocation failed for median\n"); + free(input); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + free(noise1); + free(noisy_sig); + return 1; + } + sliding_median_filter(noisy_sig, median, INPUT_LENGTH); + + double* average = (double*)malloc((INPUT_LENGTH - WINDOW_SIZE + 1) * sizeof(double)); + if (average == NULL) { + fprintf(stderr, "Memory allocation failed for average\n"); + free(input); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + free(noise1); + free(noisy_sig); + free(median); + return 1; + } + sliding_avg_filter(median, average, INPUT_LENGTH - WINDOW_SIZE + 1); + + printf("%f\n", average[3]); + + // Free allocated memory + free(input); + free(getSinDuration); + free(clean_sig); + free(getNoiseSinDuration); + free(noise); + free(noise1); + free(noisy_sig); + free(median); + free(average); + + return 0; +} + +// Function to generate a range of values +void getRangeOfVector(double* vector, double start, int length, double increment) { + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; + } +} + +// Function to apply gain (multiplier) to a signal +void gain(double* output, double* input, double multiplier, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * multiplier; + } +} + +// Function to compute the sine of each element in the input array +void sine(double* output, double* input, int length) { + for (int i = 0; i < length; i++) { + output[i] = sin(input[i]); + } +} + +// Function to find the minimum of three values +double min_of_three(double a, double b, double c) { + double min = a; + if (b < min) min = b; + if (c < min) min = c; + return min; +} + +// Function to find the maximum of three values +double max_of_three(double a, double b, double c) { + double max = a; + if (b > max) max = b; + if (c > max) max = c; + return max; +} + +// Function to apply sliding window average filter with kernel size of 3 +void sliding_avg_filter(double* input, double* output, int length) { + int new_length = length - WINDOW_SIZE + 1; + for (int i = 0; i < new_length; i++) { + output[i] = (input[i] + input[i + 1] + input[i + 2]) / 3.0; + } +} + +// Function to apply sliding window median filter with kernel size of 3 +void sliding_median_filter(double* input, double* output, int length) { + int new_length = length - WINDOW_SIZE + 1; + for (int i = 0; i < new_length; i++) { + double a = input[i]; + double b = input[i + 1]; + double c = input[i + 2]; + // Median formula: median = a + b + c - max(a, b, c) - min(a, b, c) + double max_val = max_of_three(a, b, c); + double min_val = min_of_three(a, b, c); + output[i] = a + b + c - max_val - min_val; + } +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/signalsmoothing.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/signalsmoothing.c deleted file mode 100644 index 3e7e27aeeddb..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/signalsmoothing.c +++ /dev/null @@ -1,96 +0,0 @@ -#include -#include -#include // Include math.h for sin() - -#ifndef M_PI - #define M_PI 3.14159265358979323846 // Define Pi if it's not defined -#endif - -// Function to generate an arbitrary noisy signal -void generate_noisy_signal(float *signal, int length, float noise_level) { - for (int i = 0; i < length; i++) { - // Generate a random signal value between -1.0 and 1.0 - float random_value = ((float)rand() / RAND_MAX) * 2.0f - 1.0f; // Random value in [-1, 1] - - // Add random noise - float noise = ((float)rand() / RAND_MAX - 0.5f) * 2.0f * noise_level; // Random noise in [-noise_level, noise_level] - - // Combine random value with noise - signal[i] = random_value + noise; - } -} - -// Function to print the signal -void print_signal(const char *label, float *signal, int length) { - printf("%s:\n", label); - for (int i = 0; i < length; i++) { - printf("%.4f ", signal[i]); // Display 4 digits after the decimal point - } - printf("\n"); -} - -// Function to return the maximum of three numbers -float max_of_three(float a, float b, float c) { - float max = a; - if (b > max) max = b; - if (c > max) max = c; - return max; -} - -// Function to return the minimum of three numbers -float min_of_three(float a, float b, float c) { - float min = a; - if (b < min) min = b; - if (c < min) min = c; - return min; -} - -// Function to apply sliding window average filter with kernel size of 3 -void sliding_avg_filter(float *input, float *output, int length) { - int new_length = length - 3 + 1; - for (int i = 0; i < new_length; i++) { - output[i] = (input[i] + input[i + 1] + input[i + 2]) / 3.0f; - } -} - -// Function to apply sliding window median filter with kernel size of 3 -void sliding_median_filter(float *input, float *output, int length) { - int new_length = length - 3 + 1; - for (int i = 0; i < new_length; i++) { - float a = input[i]; - float b = input[i + 1]; - float c = input[i + 2]; - // Median formula: median = a + b + c - max(a, b, c) - min(a, b, c) - float max_val = max_of_three(a, b, c); - float min_val = min_of_three(a, b, c); - output[i] = a + b + c - max_val - min_val; - } -} - -int main() { - int length = 20; // Length of the original signal - int avg_length = length - 3 + 1; // New length after filtering - int median_length = avg_length - 3 + 1; - - float signal[length]; // Original signal - float avg_filtered[avg_length]; // Signal after average filter - float median_filtered[median_length]; // Signal after median filter - - float noise_level = 0.3f; // Example noise level - - // Generate an arbitrary noisy signal - generate_noisy_signal(signal, length, noise_level); - - // Print the original signal - print_signal("Original Signal", signal, length); - - // Apply sliding window average filter - sliding_avg_filter(signal, avg_filtered, length); - print_signal("After Average Filter", avg_filtered, avg_length); - - // Apply sliding window median filter - sliding_median_filter(avg_filtered, median_filtered, avg_length); - print_signal("After Median Filter", median_filtered, median_length); - - return 0; -} diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/spaceCommunication.c b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/spaceCommunication.c index b59c53c236e6..8daf5a0605dc 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/spaceCommunication.c +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/CCode/spaceCommunication.c @@ -1,129 +1,182 @@ +#include #include #include #include -#include -#include -#define INPUT_CHAR_LENGTH 16 // number of characters -#define INPUT_LENGTH INPUT_CHAR_LENGTH*8 // number of bits - -void charToBinary(char c, char *binaryStr) { - for (int i = 7; i >= 0; i--) { - binaryStr[7 - i] = ((c >> i) & 1) ? '1' : '0'; - } - binaryStr[8] = '\0'; // Null-terminate the binary string +#define INPUT_LENGTH 100000000 + +// Function prototypes +double *getRangeOfVector(double start, int length, double increment); +void thresholdUp(const double *input, int length, double threshold, + char *output); +void space_modulate(const char *input, int *output, int length); +void transmit_and_receive(const int *signal, double *received_signal, + int length, double noise_level); +void demodulate(const double *signal, char *demodulated_data, int length); +void error_correction(const char *data, char *corrected); +void decode_data(const char *binary, char *decoded); + +// Function implementations +void thresholdUp(const double *input, int length, double threshold, + char *output) { + for (int i = 0; i < length; i++) { + output[i] = (input[i] > threshold) ? '1' : '0'; + } + output[length] = '\0'; } -// 1. Signal Conditioning (convert to binary) -void condition_signal(const char* data, char* binary) { - binary[0] = '\0'; // Ensure binary string starts empty - for (int i = 0; i < strlen(data); i++) { - char bin[9]; - charToBinary(data[i], bin); - strcat(binary, bin); - } +void space_modulate(const char *input, int *output, int length) { + for (int i = 0; i < length; i++) { + output[i] = (input[i] == '1') ? 1 : -1; + } } -// 2. Modulation (simple BPSK modulation) -void modulate(const char* binary_data, int* modulated_signal) { - for (int i = 0; i < strlen(binary_data); i++) { - modulated_signal[i] = (binary_data[i] == '1') ? 1 : -1; - } +void transmit_and_receive(const int *signal, double *received_signal, + int length, double noise_level) { + for (int i = 0; i < length; i++) { + double noise = sin(signal[i]); + received_signal[i] = signal[i] + noise; + } } -// 3. Transmission and Reception (add noise to simulate) -void transmit_and_receive(const int* signal, double* received_signal, int length, double noise_level) { - for (int i = 0; i < length; i++) { - double noise = sin(signal[i]); - received_signal[i] = signal[i] + noise; - } +void demodulate(const double *signal, char *demodulated_data, int length) { + for (int i = 0; i < length; i++) { + demodulated_data[i] = (signal[i] > 0) ? '1' : '0'; + } + demodulated_data[length] = '\0'; } -// 4. Demodulation -void demodulate(const double* signal, char* demodulated_data, int length) { - for (int i = 0; i < length; i++) { - demodulated_data[i] = (signal[i] > 0) ? '1' : '0'; +void error_correction(const char *data, char *corrected) { + int length = strlen(data); + int corrected_index = 0; + for (int i = 0; i < length; i += 8) { + int count = 0; + for (int j = 0; j < 8; j++) { + if (data[i + j] == '1') + count++; } - demodulated_data[length] = '\0'; // Null-terminate the demodulated data + if (count % 2 == 0) { + strncpy(&corrected[corrected_index], &data[i], 8); + } else { + corrected[corrected_index] = '0'; + strncpy(&corrected[corrected_index + 1], &data[i + 1], 7); + } + corrected_index += 8; + } + corrected[corrected_index] = '\0'; } -// 5. Error Correction (simple parity check) -void error_correction(const char* data, char* corrected) { - int length = strlen(data); - int corrected_index = 0; - for (int i = 0; i < length; i += 8) { - int count = 0; - for (int j = 0; j < 8; j++) { - if (data[i + j] == '1') count++; - } - if (count % 2 == 0) { - strncpy(&corrected[corrected_index], &data[i], 8); - } else { - corrected[corrected_index] = '0'; - strncpy(&corrected[corrected_index + 1], &data[i + 1], 7); - } - corrected_index += 8; - } - corrected[corrected_index] = '\0'; +void decode_data(const char *binary, char *decoded) { + int length = strlen(binary); + int decoded_index = 0; + for (int i = 0; i < length; i += 8) { + char byte[9]; + strncpy(byte, &binary[i], 8); + byte[8] = '\0'; + decoded[decoded_index++] = (char)strtol(byte, NULL, 2); + } + decoded[decoded_index] = '\0'; } -// 6. Data Decoding -void decode_data(const char* binary, char* decoded) { - int length = strlen(binary); - int decoded_index = 0; - for (int i = 0; i < length; i += 8) { - char byte[9]; - strncpy(byte, &binary[i], 8); - byte[8] = '\0'; - decoded[decoded_index++] = (char)strtol(byte, NULL, 2); - } - decoded[decoded_index] = '\0'; // Null-terminate the decoded data +double *getRangeOfVector(double start, int length, double increment) { + double *vector = malloc(length * sizeof(double)); + if (!vector) { + perror("Memory allocation failed in getRangeOfVector"); + exit(EXIT_FAILURE); + } + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; + } + return vector; } int main() { - const char* space_data = "HELLO FROM SPACE"; - char* binary; - binary = (char*)malloc(INPUT_LENGTH * sizeof(char)); - - for(int i=0; i +#include +#include + +#define PI 3.14159265359 +#define INPUT_LENGTH 100000000 +#define MAX_PEAKS 100 + +// Function declarations +void getRangeOfVector(double* vector, double start, int length, double increment); +void gain(double* output, double* input, double multiplier, int length); +void sine(double* output, double* input, int length); +void delay(double* output, double* input, int length, int delay); +void add_signals(double* output, double* input1, double* input2, int length); +void lmsFilterResponse(double* output, double* input, double* desired, double mu, int filterSize, int length); +int find_peaks(double* signal, int length, double threshold, int minDistance, int* peaks); + +int main() { + double fs = 1000; + double* input = (double*)malloc(INPUT_LENGTH * sizeof(double)); + getRangeOfVector(input, 0, INPUT_LENGTH, 0.000125); + + double getMultiplier = 2 * PI * 10; + double* getSinDuration = (double*)malloc(INPUT_LENGTH * sizeof(double)); + gain(getSinDuration, input, getMultiplier, INPUT_LENGTH); + + double* sig1 = (double*)malloc(INPUT_LENGTH * sizeof(double)); + sine(sig1, getSinDuration, INPUT_LENGTH); + + double getMultiplier2 = 2 * PI * 20; + double* getSinDuration2 = (double*)malloc(INPUT_LENGTH * sizeof(double)); + gain(getSinDuration2, input, getMultiplier2, INPUT_LENGTH); + + double* sinsig2 = (double*)malloc(INPUT_LENGTH * sizeof(double)); + sine(sinsig2, getSinDuration2, INPUT_LENGTH); + + double* sig2 = (double*)malloc(INPUT_LENGTH * sizeof(double)); + gain(sig2, sinsig2, 0.5, INPUT_LENGTH); + + double* signal = (double*)malloc(INPUT_LENGTH * sizeof(double)); + add_signals(signal, sig1, sig2, INPUT_LENGTH); + + double* noise = (double*)malloc(INPUT_LENGTH * sizeof(double)); + delay(noise, signal, INPUT_LENGTH, 5); + + double* noisy_sig = (double*)malloc(INPUT_LENGTH * sizeof(double)); + add_signals(noisy_sig, signal, noise, INPUT_LENGTH); + + double mu = 0.01; + int filterSize = 20; + double* y = (double*)malloc(INPUT_LENGTH * sizeof(double)); + lmsFilterResponse(y, noisy_sig, signal, mu, filterSize, INPUT_LENGTH); + + int peaks[MAX_PEAKS]; + int num_peaks = find_peaks(signal, INPUT_LENGTH, 1, 50, peaks); + + + printf("%d %d", peaks[1], peaks[2]); + + printf("\n"); + + // Free allocated memory + free(input); + free(getSinDuration); + free(sig1); + free(getSinDuration2); + free(sinsig2); + free(sig2); + free(signal); + free(noise); + free(noisy_sig); + free(y); + + return 0; +} + +// Function implementations + +void getRangeOfVector(double* vector, double start, int length, double increment) { + for (int i = 0; i < length; i++) { + vector[i] = start + i * increment; + } +} + +void gain(double* output, double* input, double multiplier, int length) { + for (int i = 0; i < length; i++) { + output[i] = input[i] * multiplier; + } +} + +void sine(double* output, double* input, int length) { + for (int i = 0; i < length; i++) { + output[i] = sin(input[i]); + } +} + +void delay(double* output, double* input, int length, int delay) { + for (int i = 0; i < length; i++) { + if (i < delay) { + output[i] = 0; + } else { + output[i] = input[i - delay]; + } + } +} + +void add_signals(double* output, double* input1, double* input2, int length) { + for (int i = 0; i < length; i++) { + output[i] = input1[i] + input2[i]; + } +} + +// LMS filter response function +void lmsFilterResponse(double* output, double* noisy_sig, double* clean_sig, double mu, int filterSize, int length) { + double w[32] = {0}; // Initialize weights to zero + for (int n = 0; n < length; n++) { + double y = 0; + for (int i = 0; i < filterSize; i++) { + if (n - i >= 0) { + y += w[i] * noisy_sig[n - i]; + } + } + double e = clean_sig[n] - y; + for (int i = 0; i < filterSize; i++) { + if (n - i >= 0) { + w[i] += mu * e * noisy_sig[n - i]; + } + } + output[n] = e; + } +} + +int find_peaks(double* signal, int length, double threshold, int minDistance, int* peaks) { + int num_peaks = 0; + for (int i = 1; i < length - 1 && num_peaks < MAX_PEAKS; i++) { + if (signal[i] > threshold && signal[i] > signal[i-1] && signal[i] > signal[i+1]) { + peaks[num_peaks++] = i; + i += minDistance; + } + } + return num_peaks; +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/ResultScript.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/ResultScript.py index 36c4d79100cc..0fca01e0a2bc 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/ResultScript.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/ResultScript.py @@ -16,8 +16,8 @@ # Path to the input file # Apps = "hearingAid.py" , "lowPassFull.py" , " audioCompression.py" , -# "back2backDelay.py" , "lowPassFIRFilterDesign.py" , "EnergyOfSignal.py", "periodogram2Conv1.py", "audioEqualizer.py", "vibrationAnalysis.py" -input_file_path = "vibrationAnalysis.py" +# "back2backDelay.py" , "lowPassFIRFilterDesign.py" , "EnergyOfSignal.py", "periodogram2Conv1.py", "audioEqualizer.py", "vibrationAnalysis.py", "signalSmoothing.py", "targetDetection.py", "biomedicalSignalProcessing.py", "spaceCommunication.py" +input_file_path = "spaceCommunication.py" BasePathForLLVM = "/home/local/ASURITE/apkhedka/ForLLVM/" OutputScriptPath = ( "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/" @@ -109,8 +109,8 @@ if line.strip().startswith("var input = getRangeOfVector("): # if line.strip().startswith("var N = "): # Replace the second parameter with the current value - # updated_line = f"\tvar input = getRangeOfVector(0, {value}, 0.000125);\n" - updated_line = f"\tvar input = getRangeOfVector(0, {value}, 1);\n" + updated_line = f"\tvar input = getRangeOfVector(0, {value}, 0.000125);\n" + # updated_line = f"\tvar input = getRangeOfVector(0, {value}, 1);\n" # updated_line = f" var N = {value + 1} ;\n" file.write(updated_line) else: diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/biomedicalSignalProcessing.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/biomedicalSignalProcessing.py new file mode 100644 index 000000000000..d62be189eb13 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/biomedicalSignalProcessing.py @@ -0,0 +1,56 @@ +def main() { + var fc1 = 1000; + var fc2 = 7500; + var Fs = 8000; + var N = 101; + var distance = 950; + var input = getRangeOfVector(0, 20000, 0.000125); + # print(c); + var pi = 3.14159265359; + var f_sig = 500; + var getMultiplier = 2 * pi * f_sig; + # print(getMultiplier); + var getSinDuration = gain(input, getMultiplier); + # print(getSinDuration); + var clean_sig = sin(getSinDuration ); + + var f_noise = 3000; + var getNoiseSinDuration = gain(input, 2 * pi * f_noise); + var noise = sin(getNoiseSinDuration); + var noise1 = gain(noise, 0.5); + + var noisy_sig = clean_sig + noise1; + # Step 1: FIR Bandpass Filter + var wc1 = 2 * pi * fc1 / Fs; #wc should vary from 0 to pi + var lpf1 = lowPassFIRFilter(wc1, N); #ideal low -pass filter + var lpf1_w = lpf1 * hamming(N); + + var wc2 = 2 * pi * fc2 / Fs; + var lpf2 = lowPassFIRFilter(wc2, N); + var lpf2_w = lpf2 * hamming(N); + + # var bpf = lpf2 - lpf; + var bpf_w = sub(lpf2_w,lpf1_w); + var FIRfilterResponseForBpf = FIRFilterResponse(noisy_sig, bpf_w); + + # Step 2: Artifact Removal (R-peak detection) + var max_signal = max(FIRfilterResponseForBpf); + + var height = 0.3 * max_signal; + + var r_peaks = find_peaks(FIRfilterResponseForBpf, height, distance); + + var len_r_peaks = len(r_peaks); + var last_peaks_index = sub(len_r_peaks, [1]); + var peaks_count = getSingleElemAtIndx(r_peaks, last_peaks_index); + + var diff_val = diff(r_peaks, peaks_count); + var peaks_count_minus_one = sub(peaks_count, 1); + var diff_mean = mean(diff_val, peaks_count_minus_one); + + var avg_hr = (60 * Fs) / diff_mean; + + print(avg_hr); + +} + diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/digitalModulation.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/digitalModulation.py new file mode 100644 index 000000000000..84314be04a81 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/digitalModulation.py @@ -0,0 +1,20 @@ +def main() { + # var input = [1,0,1,1,0,1,0,0]; + var input = getRangeOfVector(0, 40000, 0.000125); + # print(c); + var pi = 3.14159265359; + var f_sig = 500; + var getMultiplier = 2 * pi * f_sig; + # print(getMultiplier); + var getSinDuration = gain(input, getMultiplier); + # print(getSinDuration); + var clean_sig = sin(getSinDuration ); + var binary_sig = thresholdUp(clean_sig, 0.4,0); + var modulate_symbol_real = qam_modulate_real(binary_sig); + # print(modulate_symbol_real); + var modulate_symbol_imagine = qam_modulate_imagine(binary_sig); + # print(modulate_symbol_imagine); + var decode_data = qam_demodulate(modulate_symbol_real, modulate_symbol_imagine); + print(decode_data); +} + diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/digital_modulation.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/digital_modulation.py deleted file mode 100644 index 673059822d50..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/digital_modulation.py +++ /dev/null @@ -1,10 +0,0 @@ -def main() { - var input = [1,0,1,1,0,1,0,0]; - var modulate_symbol_real = qam_modulate_real(input); - # print(modulate_symbol_real); - var modulate_symbol_imagine = qam_modulate_imagine(input); - # print(modulate_symbol_imagine); - var decode_data = qam_demodulate(modulate_symbol_real, modulate_symbol_imagine); - print(decode_data); -} - diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/lowPassFIRFilterDesign.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/lowPassFIRFilterDesign.py index 49b7a1330827..0301bc2c4247 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/lowPassFIRFilterDesign.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/lowPassFIRFilterDesign.py @@ -8,7 +8,7 @@ def main() { # var a10 = getRangeOfVector(0, 400, 0.000125); # var orig = sin(a10); - var N = 100000001 ; + var N = 101; # for cut-off freq var pi = 3.14159265359; diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/periodogram2Conv1.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/periodogram2Conv1.py index 5ba84b852663..81a04255f6f6 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/periodogram2Conv1.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/periodogram2Conv1.py @@ -27,26 +27,7 @@ def main() { var fft_img = fft1dimg(conv1d); var sq = fft_real * fft_real + fft_img * fft_img; print(sq); - # var final1 = getElemAtIndx(fft_real , [6]); - # var final2 = getElemAtIndx(fft_real , [7]); - # print(final1); - # print(final2); - # print(conv1d); - # print(fft_real); - # print(fft_img); - #Pad the input , reverse_input for the size of conv o/p - #Calculate - # var padLen = 9 ; #10 + 10 - 1 - 10 - # var input_padded = padding(input , 0, padLen ); - - - # var fft10real = fft1dreal(input); - # var fft10img = fft1dimg(input); - - # #try input * -input - # var neg_input = gain(input , -1); - # var sq = fft10real * fft10real + fft10img * fft10img; - # print(sq); - + var final1 = getElemAtIndx(sq , [2]); + print(final1); } diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/signalSmoothing.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/signalSmoothing.py new file mode 100644 index 000000000000..f93429e2c2c5 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/signalSmoothing.py @@ -0,0 +1,20 @@ +def main() { + var fs = 8000; + var input = getRangeOfVector(0, 100000000, 0.000125); + var f_sig = 500; + var pi = 3.14159265359; + var getMultiplier = 2 * pi * f_sig; + var getSinDuration = gain(input, getMultiplier); + var clean_sig = sin(getSinDuration ); + var f_noise = 3000; + var getNoiseSinDuration = gain(input, 2 * pi * f_noise); + var noise = sin(getNoiseSinDuration); + var noise1 = gain(noise, 0.5); + + var noisy_sig = clean_sig + noise1; + var median = medianFilter(noisy_sig); + var average = slidingWindowAvg(median); +# print(average); + var final1 = getElemAtIndx(average , [1]); + print(final1); +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/signalsmoothing.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/signalsmoothing.py deleted file mode 100644 index 3af1883cd36c..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/signalsmoothing.py +++ /dev/null @@ -1,6 +0,0 @@ -def main() { - var input = getRangeOfVector(0, 20, 1); - var average = slidingWindowAvg(input); - var median = slidingWindowAvg(average); - print(median); -} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/spaceCommunication.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/spaceCommunication.py index 1fd7c5cd6343..77a1b92c3b4e 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/spaceCommunication.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/spaceCommunication.py @@ -1,14 +1,13 @@ def main() { - var input = "HELLO FROM SPACE"; + # var input = "HELLO FROM SPACE"; + var input = getRangeOfVector(0, 100000000, 1); # print(input); - var a = space_modulate(input); + var binary_sig = thresholdUp(input,50,0); + var a = space_modulate(binary_sig); var noise = sin(a); var noisy_signal = a+noise; var b = space_demodulate(noisy_signal); var e = space_err_correction(b); - print(e); -} - -# ./bin/dsp1 ../mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/spaceCommunication.py -emit=jit 2> input.txt -# ./bin/dsp1 ../mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/spaceCommunication.py -emit=jit 2> output.txt -# diff input.txt ouput.txt \ No newline at end of file + var final = getElemAtIndx(e, [8]); + print(final); +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/targetDetection.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/targetDetection.py new file mode 100644 index 000000000000..6f07f39097a5 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/targetDetection.py @@ -0,0 +1,27 @@ +def main() { + var fs = 1000; + # var step = 1/fs; + # print(step); + var input = getRangeOfVector(0, 100000000, 0.000125); + var pi = 3.14159265359; + var getMultiplier = 2 * pi * 10; + # print(getMultiplier); + var getSinDuration = gain(input, getMultiplier); + var sig1 = sin(getSinDuration ); + var getMultiplier2 = 2 * pi * 20; + var getSinDuration2 = gain(input, getMultiplier2); + var sinsig2 = sin(getSinDuration2); + var sig2 = gain(sinsig2, 0.5); + var signal = sig1 + sig2; + var noise = delay(signal, 5); + var noisy_sig = signal + noise; + + var mu = 0.01; + var filterSize = 20; + var y = lmsFilterResponse(noisy_sig, signal, mu, filterSize); + var peaks = find_peaks(y, 1, 50); + var final1 = getElemAtIndx(peaks , [1]); + var final2 = getElemAtIndx(peaks , [2]); + print(final1); + print(final2); +} \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/vibrationAnalysis.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/vibrationAnalysis.py index 2116ba1cb2e4..d1b2cdb1e3a3 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/vibrationAnalysis.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/vibrationAnalysis.py @@ -22,10 +22,10 @@ def main() { var sq_abs = square(fft_real) + square(fft_img) ; # sum = sum(sq_abs) - var sum1 = sum(sq_abs); - # res = gain(sum , 1/N) - var len1 = len(input); - var res = sum1 / len1; + # var sum1 = sum(sq_abs); + # # res = gain(sum , 1/N) + # var len1 = len(input); + # var res = sum1 / len1; # print(sq_abs); var GetThresholdReal = threshold( sq_abs , threshold); print(GetThresholdReal); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/ResultScript.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/ResultScript.py index c806354a0624..57e02370beab 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/ResultScript.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/ResultScript.py @@ -15,8 +15,8 @@ # Run the respective commands on the file # Path to the input file -# Apps = "noiseCancelling.m" , "echoCancelling.m", "periodogram.m", "lowPassFull.m", "hearingAid.m", "lowPassFIRFilterDesign", "energyOfSignal", "audioEqualizer", "audioCompression","vibrationAnalysis", "underWaterCommunication", "voiceActivityDetection" -input_file = "lowPassFIRFilterDesign" +# Apps = "noiseCancelling.m" , "echoCancelling.m", "periodogram.m", "lowPassFull.m", "hearingAid.m", "lowPassFIRFilterDesign", "energyOfSignal", "audioEqualizer", "audioCompression","vibrationAnalysis", "underWaterCommunication", "voiceActivityDetection", "signalSmoothing", "targetDetection", "biomedicalSignalProcessing" +input_file = "biomedicalSignalProcessing" input_file_path = input_file + ".m" BasePathForLLVM = "/home/local/ASURITE/apkhedka/ForLLVM/" OutputScriptPath = "mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/" diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/biomedicalSignalProcessing.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/biomedicalSignalProcessing.m new file mode 100644 index 000000000000..9212913001f2 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/biomedicalSignalProcessing.m @@ -0,0 +1,60 @@ +% Constants +INPUT_LENGTH = 20000000; +MAX_PEAKS = 1000; +N = 101; + +% Signal parameters +fc1 = 1000; +fc2 = 7500; +Fs = 8000; +distance = 950; +f_sig = 500; +f_noise = 3000; + +% Generate input signal +t = (0:0.000125:(INPUT_LENGTH-1)*0.000125)'; + +% Generate clean signal +clean_sig = sin(2*pi*f_sig*t); + +% Generate noise +noise = 0.5 * sin(2*pi*f_noise*t); + +% Create noisy signal +noisy_sig = clean_sig + noise; + +% Step 1: FIR Bandpass Filter +wc1 = 2 * pi * fc1 / Fs; +wc2 = 2 * pi * fc2 / Fs; + +% Design lowpass filters +n = 0:N-1; +mid = (N-1)/2; +lpf1 = (wc1/pi) * sinc(wc1*(n-mid)/pi); +lpf2 = (wc2/pi) * sinc(wc2*(n-mid)/pi); + +% Apply Hamming window +hamming_window = hamming(N)'; +lpf1_w = lpf1 .* hamming_window; +lpf2_w = lpf2 .* hamming_window; + +% Create bandpass filter +bpf_w = lpf2_w - lpf1_w; + +% Apply bandpass filter +FIRfilterResponseForBpf = filter(bpf_w, 1, noisy_sig); + +% Step 2: Artifact Removal (R-peak detection) +max_val = max(FIRfilterResponseForBpf); +height = 0.3 * max_val; + +% Find peaks +[~, r_peaks] = findpeaks(FIRfilterResponseForBpf, 'MinPeakHeight', height, 'MinPeakDistance', distance); + +% Calculate heart rate +diff_val = diff(r_peaks); +diff_mean = mean(diff_val); + +avg_hr = (60 * Fs) / diff_mean; + +fprintf('%f\n', avg_hr); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/signalSmoothing.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/signalSmoothing.m new file mode 100644 index 000000000000..0753b6e78ef9 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/signalSmoothing.m @@ -0,0 +1,30 @@ +% Define constants +INPUT_LENGTH = 1000; +SAMPLE_RATE = 8000; +step = 0.000125; +WINDOW_SIZE = 3; + +% Generate input range +input = (0:step:(INPUT_LENGTH-1)*step)'; + +% Signal parameters +f_sig = 500; +f_noise = 3000; + +% Generate clean signal +clean_sig = sin(2*pi*f_sig*input); + +% Generate noise +noise = 0.5 * sin(2*pi*f_noise*input); + +% Create noisy signal +noisy_sig = clean_sig + noise; + +% Apply median filter +median_filtered = medfilt1(noisy_sig, WINDOW_SIZE); + +% Apply moving average filter +avg_filtered = movmean(median_filtered, WINDOW_SIZE); + +% Print the 4th element of the final result +disp(avg_filtered(4)); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/signalsmoothing.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/signalsmoothing.m deleted file mode 100644 index 7def04cce883..000000000000 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/signalsmoothing.m +++ /dev/null @@ -1,40 +0,0 @@ -% Define a function to generate an arbitrary noisy signal -function signal = generate_noisy_signal(length, noise_level) - % Initialize the signal - signal = zeros(1, length); - for i = 1:length - % Generate a random signal value between -1.0 and 1.0 - random_value = rand() * 2 - 1; % Random value in [-1, 1] - - % Add random noise - noise = (rand() - 0.5) * 2 * noise_level; % Random noise in [-noise_level, noise_level] - - % Combine random value with noise - signal(i) = random_value + noise; - end -end - -% Main script -length = 20; % Length of the original signal -noise_level = 0.3; % Example noise level - -% Generate an arbitrary noisy signal -signal = generate_noisy_signal(length, noise_level); - -% Print the original signal -fprintf('Original Signal:\n'); -fprintf('%.4f ', signal); - -% Apply moving average filter with kernel size of 3 -avg_filtered = movmean(signal, 3, 'Endpoints', 'discard'); - -% Print the output after applying the moving average filter -fprintf('After Moving Average Filter:\n'); -fprintf('%.4f ', avg_filtered); - -% Apply moving median filter to the output of the moving average filter -median_filtered = movmedian(avg_filtered, 3, 'Endpoints', 'discard'); - -% Print the output after applying the moving median filter -fprintf('After Moving Median Filter:\n'); -fprintf('%.4f ', median_filtered); diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/targetDetection.m b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/targetDetection.m new file mode 100644 index 000000000000..fda717133060 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/Matlab/targetDetection.m @@ -0,0 +1,51 @@ +% Constants +INPUT_LENGTH = 100000000; +MAX_PEAKS = 100; + +% Generate input range +input = (0:0.000125:(INPUT_LENGTH-1)*0.000125)'; + +% Generate signals +getMultiplier = 2 * pi * 10; +getSinDuration = input * getMultiplier; +sig1 = sin(getSinDuration); + +getMultiplier2 = 2 * pi * 20; +getSinDuration2 = input * getMultiplier2; +sinsig2 = sin(getSinDuration2); +sig2 = 0.5 * sinsig2; + +% Combine signals +signal = sig1 + sig2; + +% Add delayed noise +noise = [zeros(5, 1); signal(1:end-5)]; +noisy_sig = signal + noise; + +% LMS Filter +mu = 0.01; +filterSize = 20; +y = lmsFilterResponse(noisy_sig, signal, mu, filterSize); + +% Find peaks +[peaks, ~] = findpeaks(signal, 'MinPeakHeight', 1, 'MinPeakDistance', 50); + +% Display results +fprintf('%d %d\n', peaks(2), peaks(3)); + + +% LMS Filter Response Function +function output = lmsFilterResponse(noisy_sig, clean_sig, mu, filterSize) + length = numel(noisy_sig); + w = zeros(filterSize, 1); + output = zeros(length, 1); + + for n = 1:length + x = noisy_sig(max(1, n-filterSize+1):n); + x = [zeros(filterSize - numel(x), 1); x]; + y = w' * x; + e = clean_sig(n) - y; + w = w + mu * e * x; + output(n) = e; + end +end \ No newline at end of file diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 34e78a6ba417..56c982e7b280 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -1407,6 +1407,23 @@ def Diff2MeanOptimizedOp : Dsp_Op<"diff2meanOpt", } +//===----------------------------------------------------------------------===// +// LMS2FindPeaksOptimizedOp +//===----------------------------------------------------------------------===// + +def LMS2FindPeaksOptimizedOp : Dsp_Op<"lms2findPeaks", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Fusing loop for LMSFilterResponseOp and FindPeaksOp"; + + let arguments = (ins F64Tensor:$lhs, F64Tensor:$rhs, F64Tensor:$mu, F64Tensor:$filterLen, F64Tensor:$height, F64Tensor:$distance); + let results = (outs F64Tensor); + + let builders = [ + OpBuilder<(ins "Value":$lhs, "Value":$rhs, "Value":$mu, "Value":$filterLen, "Value":$height, "Value":$distance)> + ]; +} + + @@ -2331,6 +2348,8 @@ def FindPeaksOp : Dsp_Op<"find_peaks", [Pure , DeclareOpInterfaceMethods(); + DenseElementsAttr constantDistanceValue = constantOpDistance.getValue(); + + auto elements = constantDistanceValue.getValues(); + float distanceFloat = elements[0].getValueAsDouble(); + //SecondValueInt = (int64_t)SecondValue; + + int64_t sizeOfOutput = (len_signal-1)/distanceFloat + 2; + + std::vector shapeForOutput; + shapeForOutput.push_back(sizeOfOutput); + + mlir::TensorType manipulatedType = mlir::RankedTensorType::get( + shapeForOutput, signalType.getElementType()); + + getResult().setType(manipulatedType); + +} + + + //===----------------------------------------------------------------------===// // SetElemAtIndxOp //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index dad67948a22c..bd5e7ccabc54 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -1298,44 +1298,18 @@ struct FFT1DRealSymmOpLowering : public ConversionPattern { int64_t ubBy2 = (ub + 1) / 2; int64_t step = 1; - // load from X, & y1 & y2 - FFT1DRealSymmOpAdaptor fft1DRealSymmAdaptor(operands); - // affine::AffineForOp forOp1 = rewriter.create(loc, lb, ub, // step); auto iv = forOp1.getInductionVar(); // rewriter.setInsertionPointToStart(forOp1.getBody()); // rewriter.create(loc, constant0, alloc_real, // ValueRange{iv}); rewriter.setInsertionPointAfter(forOp1); - - // k=0 - // sum=0 - // for n= 0 to N - // sum = sum + x[n] - // y[0] = sum - affine::AffineForOp forOp2 = rewriter.create( - loc, lb, ub, step, ValueRange{constant0}); - auto iv2 = forOp2.getInductionVar(); - rewriter.setInsertionPointToStart(forOp2.getBody()); - // get previous sum - auto getIterArg1 = forOp2.getBody()->getArgument(1); - Value loadX = rewriter.create( - loc, fft1DRealSymmAdaptor.getInput(), ValueRange{iv2}); - Value sumNext1 = rewriter.create(loc, loadX, getIterArg1); - rewriter.create(loc, ValueRange{sumNext1}); - rewriter.setInsertionPointAfter(forOp2); - - // store result for k=0 + DEBUG_PRINT_NO_ARGS(); + // for k=0 Value Indx0 = rewriter.create(loc, 0); - rewriter.create(loc, forOp2.getResult(0), alloc_real, + rewriter.create(loc, constant0, alloc_real, ValueRange{Indx0}); - // for k=1 to (N+1)/2 - // sum = 0 - // for n=0 to N - // sum = sum + x[n] * cos(2*pi*k*n/N) - // y[k] = sum - // y[N-k] = sum - // loop for Y ie, k + // loop for Y affine::AffineForOp forOpY = rewriter.create(loc, lb + 1, ubBy2, step); auto ivY = forOpY.getInductionVar(); @@ -1345,14 +1319,14 @@ struct FFT1DRealSymmOpLowering : public ConversionPattern { affine::AffineForOp forOpX = rewriter.create(loc, lb, ub, step, ValueRange{constant0}); auto ivX = forOpX.getInductionVar(); - // get sum auto getIterArg = forOpX.getBody()->getArgument(1); rewriter.setInsertionPointToStart(forOpX.getBody()); // load from X, & y1 & y2 + FFT1DRealSymmOpAdaptor fft1DRealSymmAdaptor(operands); Value inputX = rewriter.create( loc, fft1DRealSymmAdaptor.getInput(), ValueRange{ivX}); - // Value loadYReal = rewriter.create(loc, alloc_real, + // Value loadYImg = rewriter.create(loc, alloc_img, // ValueRange{ivY}); // convert index to f64 @@ -1373,6 +1347,9 @@ struct FFT1DRealSymmOpLowering : public ConversionPattern { loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(6.28318530718)); Value mul2piKI = rewriter.create(loc, const2pi, muli_k); + // getOperand().getType() + // auto inputTensorType = + // llvm::cast(op->getOperand(0).getType()); float LengthOfInput = (float)ub; Value N = rewriter.create( loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(LengthOfInput)); @@ -1380,7 +1357,6 @@ struct FFT1DRealSymmOpLowering : public ConversionPattern { Value divIndxByN = rewriter.create(loc, mul2piKI, N); - // Real part = Sum(x[i] * cos(div) ) Value GetCos = rewriter.create(loc, divIndxByN); Value xMulCos = rewriter.create(loc, inputX, GetCos); @@ -1401,13 +1377,11 @@ struct FFT1DRealSymmOpLowering : public ConversionPattern { AffineExpr ExprNminusK = rewriter.getAffineConstantExpr(ub) - rewriter.getAffineDimExpr(0); AffineMap mapNminusK = AffineMap::get(1, 0, ExprNminusK); + rewriter.create(loc, forOpX.getResult(0), alloc_real, mapNminusK, ValueRange{ivY}); rewriter.setInsertionPointAfter(forOpY); - // debug - // forOpX->dump(); - // forOpY->dump(); rewriter.replaceOp(op, alloc_real); return success(); @@ -2878,6 +2852,8 @@ struct LMSFilterResponseOpLowering : public ConversionPattern { // Pseudo-code: // for (int n = 0; n < NUM_SAMPLES; n++) { + // // we also need to initialize w + // // w[n] = 0; // // Calculate the filter output y[n] // y[n] = 0; // for (int i = 0; i < FILTER_LENGTH; i++) { @@ -2947,11 +2923,13 @@ struct LMSFilterResponseOpLowering : public ConversionPattern { AffineMap addMapForLMSFilter = AffineMap::get(2, 0, ExprForXSlice); IntegerSet set1 = IntegerSet::get(2, 0, {ExprForXSlice}, {false}); + // w[n] = 0; // y[n] = 0; // rewriter.create(loc, zeroval, alloc, ValueRange{iv}); // Allocate and initialize array for y // Value constantIndx0 = rewriter.create(loc, 0); + rewriter.create(loc, zeroval, wAlloc, ValueRange{iv}); rewriter.create(loc, zeroval, alloc, ValueRange{iv}); affine::AffineForOp forOp2 = @@ -7319,6 +7297,9 @@ struct FindPeaksOpLowering : public ConversionPattern { Value distance = rewriter.create( loc, rewriter.getIndexType(), distance_ui); + auto height = rewriter.create( + loc, findPeaksOpAdaptor.getHeight(), heightValueRange); + affine::AffineForOp forOpInit = rewriter.create(loc, 0, tensorType.getShape()[0], step); auto init_iter = forOpInit.getInductionVar(); @@ -7357,8 +7338,6 @@ struct FindPeaksOpLowering : public ConversionPattern { auto signal_next = rewriter.create(loc, findPeaksOpAdaptor.getSignal(), addMapForNext, ValueRange{current_index}); - auto height = rewriter.create( - loc, findPeaksOpAdaptor.getHeight(), heightValueRange); //%cmp_current_prev = arith.cmpf ogt, %signal_current, %signal_prev : f64 //%cmp_current_next = arith.cmpf ogt, %signal_current, %signal_next : f64 @@ -7824,6 +7803,488 @@ struct Diff2MeanOptimizedOpLowering : public ConversionPattern { } }; +struct LMS2FindPeaksOptimizedOpLowering : public ConversionPattern { + LMS2FindPeaksOptimizedOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::LMS2FindPeaksOptimizedOp::getOperationName(), 1, + ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + // Pseudo-code: + // for (int n = 0; n < NUM_SAMPLES; n++) { + // // Calculate the filter output y[n] + // y[n] = 0; + // for (int i = 0; i < FILTER_LENGTH; i++) { + // if (n - i >= 0) { // affine if + // y[n] = y[n] + (w[i] * x[n - i]); + // } + // } + + // // Calculate the error e[n] + // e[n] = d[n] - y[n]; + + // // Update the filter weights w[i] + // for (int i = 0; i < FILTER_LENGTH; i++) { + // if (n - i >= 0) { + // w[i] += MU * e[n] * x[n - i]; + // } + // } + // } + + auto tensorType = llvm::cast((*op->result_type_begin())); + auto lhsType = + llvm::dyn_cast(op->getOperand(0).getType()); + + ArrayRef lhsShape = lhsType.getShape(); + + // allocation & deallocation for the result of this operation + auto memRefType = MemRefType::get(lhsShape, rewriter.getF64Type()); + auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); + + auto memRefTypeOutput = convertTensorToMemRef(tensorType); + auto alloc_output = insertAllocAndDealloc(memRefTypeOutput, loc, rewriter); + + auto countMemRefType = MemRefType::get({}, rewriter.getIndexType()); + auto alloc_peaks_count = + insertAllocAndDealloc(countMemRefType, loc, rewriter); + + // construct affine loops for the input + SmallVector lowerBounds(lhsType.getRank(), /*Value*/ 0); + SmallVector steps(lhsType.getRank(), /*Value=*/1); + + typename dsp::LMS2FindPeaksOptimizedOp::Adaptor lfr2fpAdaptor(operands); + + // Value alpha = rewriter.create(loc, + // rewriter.getF64Type(), + // rewriter.getF64FloatAttr(1)); + Value zeroval = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value mu = rewriter.create(loc, lfr2fpAdaptor.getMu()); + + Value cst_idx_zero = rewriter.create(loc, 0); + Value cst_idx_one = rewriter.create(loc, 1); + Value constant_minus_one = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(-1)); + + // initialization for findPeaks + rewriter.create(loc, cst_idx_zero, alloc_peaks_count, + ValueRange{}); + + auto heightArgType = + llvm::dyn_cast(op->getOperand(4).getType()); + + int heightArgShape = heightArgType.getShape().size(); + + ValueRange heightValueRange; + + if (heightArgShape == 0) + heightValueRange = ValueRange{}; + else + heightValueRange = ValueRange{cst_idx_zero}; + + auto distanceArgType = + llvm::dyn_cast(op->getOperand(5).getType()); + + int distanceArgShape = distanceArgType.getShape().size(); + + ValueRange distanceValueRange; + + if (distanceArgShape == 0) + distanceValueRange = ValueRange{}; + else + distanceValueRange = ValueRange{cst_idx_zero}; + + auto distance_fp = rewriter.create( + loc, lfr2fpAdaptor.getDistance(), distanceValueRange); + Value distance_ui = rewriter.create( + loc, rewriter.getIntegerType(32), distance_fp); + Value distance = rewriter.create( + loc, rewriter.getIndexType(), distance_ui); + + auto height = rewriter.create( + loc, lfr2fpAdaptor.getHeight(), heightValueRange); + + affine::AffineForOp forOpInit = + rewriter.create(loc, 0, tensorType.getShape()[0], 1); + auto init_iter = forOpInit.getInductionVar(); + rewriter.setInsertionPointToStart(forOpInit.getBody()); + + rewriter.create(loc, constant_minus_one, alloc_output, + ValueRange{init_iter}); + + rewriter.setInsertionPointAfter(forOpInit); + + // unrolled two iterations. + int64_t lb = 0; + int64_t step = 1; + + Value GetFilterLOp = op->getOperand(3); + dsp::ConstantOp constantOp3rdArg = + GetFilterLOp.getDefiningOp(); + DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue(); + + auto elements1 = constant3rdValue.getValues(); + float filterlenval = elements1[0].getValueAsDouble(); + auto FilterLength = (uint64_t)filterlenval; + + int64_t numSamples = lhsType.getShape()[0]; + + auto yMemRefType = MemRefType::get({numSamples}, rewriter.getF64Type()); + // auto wAlloc = rewriter.create(loc, yMemRefType); + auto wAlloc = insertAllocAndDealloc(yMemRefType, loc, rewriter); + + // For affine expression: #map1 = affine_map<(%arg0)[] : (%arg0 - 1) + AffineExpr d0, d1, s0; + bindDims(rewriter.getContext(), d0, d1); + // AffineExpr ExprForXSlice = rewriter.getAffineDimExpr(0) - + // rewriter.getAffineDimExpr(1); //d0 - d1; + AffineExpr ExprForXSlice = d0 - d1; + AffineMap addMapForLMSFilter = AffineMap::get(2, 0, ExprForXSlice); + IntegerSet set1 = IntegerSet::get(2, 0, {ExprForXSlice}, {false}); + + { + + // w[n] = 0; + // y[n] = 0; + // rewriter.create(loc, zeroval, alloc, ValueRange{iv}); + // Allocate and initialize array for y + // Value constantIndx0 = rewriter.create(loc, 0); + rewriter.create(loc, zeroval, wAlloc, + ValueRange{cst_idx_zero}); + rewriter.create(loc, zeroval, alloc, + ValueRange{cst_idx_zero}); + + affine::AffineForOp forOp2 = + rewriter.create(loc, lb, FilterLength, step); + auto iv2 = forOp2.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp2.getBody()); + + auto ifOp = rewriter.create( + loc, set1, ValueRange{cst_idx_zero, iv2}, false /*no else*/); + rewriter.setInsertionPointToStart(ifOp.getThenBlock()); + + Value inputX = rewriter.create( + loc, lfr2fpAdaptor.getLhs(), addMapForLMSFilter, + ValueRange{cst_idx_zero, iv2}); + Value w = rewriter.create(loc, wAlloc, + ValueRange{iv2}); // memRefType + + Value wmulx = rewriter.create(loc, inputX, w); + Value ybefore = + rewriter.create(loc, alloc, ValueRange{cst_idx_zero}); + Value sumNext = rewriter.create(loc, wmulx, ybefore); + rewriter.create(loc, sumNext, alloc, + ValueRange{cst_idx_zero}); + rewriter.setInsertionPointAfter(ifOp); + rewriter.setInsertionPointAfter(forOp2); + + // get e[n] = d[n] - y[n] + + Value desiredX = rewriter.create( + loc, lfr2fpAdaptor.getRhs(), ValueRange{cst_idx_zero}); + Value ynew = + rewriter.create(loc, alloc, ValueRange{cst_idx_zero}); + + Value err = rewriter.create(loc, desiredX, ynew); + + affine::AffineForOp forOp3 = + rewriter.create(loc, lb, FilterLength, step); + auto iv3 = forOp3.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp3.getBody()); + + auto ifOp2 = rewriter.create( + loc, set1, ValueRange{cst_idx_zero, iv3}, false /*no else*/); + rewriter.setInsertionPointToStart(ifOp2.getThenBlock()); + + Value inputX2 = rewriter.create( + loc, lfr2fpAdaptor.getLhs(), addMapForLMSFilter, + ValueRange{cst_idx_zero, iv3}); + + Value Prevw2 = + rewriter.create(loc, wAlloc, ValueRange{iv3}); + + // f(u(n),e(n),μ)=μe(n)u∗(n) + Value mul1 = rewriter.create(loc, err, inputX2); + Value mul2 = rewriter.create(loc, mu, mul1); + + // FInal w[n] + Value answer = rewriter.create(loc, Prevw2, mul2); + + rewriter.create(loc, answer, wAlloc, ValueRange{iv3}); + + rewriter.setInsertionPointAfter(ifOp2); + rewriter.setInsertionPointAfter(forOp3); + } + + { + // w[n] = 0; + // y[n] = 0; + // rewriter.create(loc, zeroval, alloc, ValueRange{iv}); + // Allocate and initialize array for y + // Value constantIndx0 = rewriter.create(loc, 0); + rewriter.create(loc, zeroval, wAlloc, + ValueRange{cst_idx_one}); + rewriter.create(loc, zeroval, alloc, + ValueRange{cst_idx_one}); + + affine::AffineForOp forOp2 = + rewriter.create(loc, lb, FilterLength, step); + auto iv2 = forOp2.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp2.getBody()); + + auto ifOp = rewriter.create( + loc, set1, ValueRange{cst_idx_one, iv2}, false /*no else*/); + rewriter.setInsertionPointToStart(ifOp.getThenBlock()); + + Value inputX = rewriter.create( + loc, lfr2fpAdaptor.getLhs(), addMapForLMSFilter, + ValueRange{cst_idx_one, iv2}); + Value w = rewriter.create(loc, wAlloc, + ValueRange{iv2}); // memRefType + + Value wmulx = rewriter.create(loc, inputX, w); + Value ybefore = + rewriter.create(loc, alloc, ValueRange{cst_idx_one}); + Value sumNext = rewriter.create(loc, wmulx, ybefore); + rewriter.create(loc, sumNext, alloc, + ValueRange{cst_idx_one}); + rewriter.setInsertionPointAfter(ifOp); + rewriter.setInsertionPointAfter(forOp2); + + // get e[n] = d[n] - y[n] + + Value desiredX = rewriter.create( + loc, lfr2fpAdaptor.getRhs(), ValueRange{cst_idx_one}); + Value ynew = + rewriter.create(loc, alloc, ValueRange{cst_idx_one}); + + Value err = rewriter.create(loc, desiredX, ynew); + + affine::AffineForOp forOp3 = + rewriter.create(loc, lb, FilterLength, step); + auto iv3 = forOp3.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp3.getBody()); + + auto ifOp2 = rewriter.create( + loc, set1, ValueRange{cst_idx_one, iv3}, false /*no else*/); + rewriter.setInsertionPointToStart(ifOp2.getThenBlock()); + + Value inputX2 = rewriter.create( + loc, lfr2fpAdaptor.getLhs(), addMapForLMSFilter, + ValueRange{cst_idx_one, iv3}); + + Value Prevw2 = + rewriter.create(loc, wAlloc, ValueRange{iv3}); + + // f(u(n),e(n),μ)=μe(n)u∗(n) + Value mul1 = rewriter.create(loc, err, inputX2); + Value mul2 = rewriter.create(loc, mu, mul1); + + // FInal w[n] + Value answer = rewriter.create(loc, Prevw2, mul2); + + rewriter.create(loc, answer, wAlloc, ValueRange{iv3}); + + rewriter.setInsertionPointAfter(ifOp2); + rewriter.setInsertionPointAfter(forOp3); + } + + // Outer for loop -- iterate from 2 to last + int64_t lb_outer = 2; + + affine::AffineForOp forOp1 = + rewriter.create(loc, lb_outer, numSamples, step); + auto iv = forOp1.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp1.getBody()); + // w[n] = 0; + // y[n] = 0; + // rewriter.create(loc, zeroval, alloc, ValueRange{iv}); + // Allocate and initialize array for y + // Value constantIndx0 = rewriter.create(loc, 0); + + rewriter.create(loc, zeroval, wAlloc, ValueRange{iv}); + rewriter.create(loc, zeroval, alloc, ValueRange{iv}); + + affine::AffineForOp forOp2 = + rewriter.create(loc, lb, FilterLength, step); + auto iv2 = forOp2.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp2.getBody()); + + auto ifOp = rewriter.create( + loc, set1, ValueRange{iv, iv2}, false /*no else*/); + rewriter.setInsertionPointToStart(ifOp.getThenBlock()); + + Value inputX = rewriter.create( + loc, lfr2fpAdaptor.getLhs(), addMapForLMSFilter, ValueRange{iv, iv2}); + Value w = rewriter.create(loc, wAlloc, + ValueRange{iv2}); // memRefType + + Value wmulx = rewriter.create(loc, inputX, w); + Value ybefore = rewriter.create(loc, alloc, ValueRange{iv}); + Value sumNext = rewriter.create(loc, wmulx, ybefore); + rewriter.create(loc, sumNext, alloc, ValueRange{iv}); + rewriter.setInsertionPointAfter(ifOp); + rewriter.setInsertionPointAfter(forOp2); + + // get e[n] = d[n] - y[n] + + Value desiredX = rewriter.create(loc, lfr2fpAdaptor.getRhs(), + ValueRange{iv}); + Value ynew = rewriter.create(loc, alloc, ValueRange{iv}); + + Value err = rewriter.create(loc, desiredX, ynew); + + affine::AffineForOp forOp3 = + rewriter.create(loc, lb, FilterLength, step); + auto iv3 = forOp3.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp3.getBody()); + + auto ifOp2 = rewriter.create( + loc, set1, ValueRange{iv, iv3}, false /*no else*/); + rewriter.setInsertionPointToStart(ifOp2.getThenBlock()); + + Value inputX2 = rewriter.create( + loc, lfr2fpAdaptor.getLhs(), addMapForLMSFilter, ValueRange{iv, iv3}); + + Value Prevw2 = rewriter.create(loc, wAlloc, ValueRange{iv3}); + + // f(u(n),e(n),μ)=μe(n)u∗(n) + Value mul1 = rewriter.create(loc, err, inputX2); + Value mul2 = rewriter.create(loc, mu, mul1); + + // FInal w[n] + Value answer = rewriter.create(loc, Prevw2, mul2); + + rewriter.create(loc, answer, wAlloc, ValueRange{iv3}); + rewriter.setInsertionPointAfter(ifOp2); + rewriter.setInsertionPointAfter(forOp3); + + // HERE WE SHOULD INSERT FIND_PEAKS FOR FUSING LOOP + + AffineExpr ExprForPrev = + rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(2); + AffineMap addMapForPrev = AffineMap::get(1, 0, ExprForPrev); + + AffineExpr ExprForCurrent = + rewriter.getAffineDimExpr(0) - rewriter.getAffineConstantExpr(1); + AffineMap addMapForCurrent = AffineMap::get(1, 0, ExprForCurrent); + + auto signal_prev = rewriter.create(loc, alloc, addMapForPrev, + ValueRange{iv}); + auto signal_current = rewriter.create( + loc, alloc, addMapForCurrent, ValueRange{iv}); + auto signal_next = + rewriter.create(loc, alloc, ValueRange{iv}); + + auto cmp_current_prev = rewriter.create( + loc, arith::CmpFPredicate::OGT, signal_current, signal_prev); + auto cmp_current_next = rewriter.create( + loc, arith::CmpFPredicate::OGT, signal_current, signal_next); + auto cmp_current_height = rewriter.create( + loc, arith::CmpFPredicate::OGE, signal_current, height); + + auto and_two_cmps = + rewriter.create(loc, cmp_current_prev, cmp_current_next); + auto and_three_cmps = + rewriter.create(loc, and_two_cmps, cmp_current_height); + + auto firstIfOp = + rewriter.create(loc, and_three_cmps, false /* else=1 */); + rewriter.setInsertionPointToStart(firstIfOp.thenBlock()); + + auto peaks_count = rewriter.create( + loc, alloc_peaks_count, ValueRange{}); + auto cmp_new_peak = rewriter.create( + loc, arith::CmpIPredicate::eq, peaks_count, cst_idx_zero); + + auto current_index = rewriter.create(loc, iv, cst_idx_one); + + auto secondIfOp = + rewriter.create(loc, cmp_new_peak, true /* else=1 */); + rewriter.setInsertionPointToStart(secondIfOp.thenBlock()); + Value current_index_to_ui = rewriter.create( + loc, rewriter.getIntegerType(32), current_index); + Value current_index_to_f64 = rewriter.create( + loc, rewriter.getF64Type(), current_index_to_ui); + rewriter.create(loc, current_index_to_f64, alloc_output, + ValueRange{peaks_count}); + auto peaks_count_inc = + rewriter.create(loc, peaks_count, cst_idx_one); + rewriter.create(loc, peaks_count_inc, alloc_peaks_count, + ValueRange{}); + + rewriter.setInsertionPointToStart(secondIfOp.elseBlock()); + + Value last_peaks_count = + rewriter.create(loc, peaks_count, cst_idx_one); + auto last_peak_index_fp = rewriter.create( + loc, alloc_output, ValueRange{last_peaks_count}); + Value last_peak_index_ui = rewriter.create( + loc, rewriter.getIntegerType(32), last_peak_index_fp); + Value last_peak_index = rewriter.create( + loc, rewriter.getIndexType(), last_peak_index_ui); + Value subtract_current_index_last_peak = + rewriter.create(loc, current_index, last_peak_index); + auto cmp_sub_distance = rewriter.create( + loc, arith::CmpIPredicate::sge, subtract_current_index_last_peak, + distance); + + auto thirdIfOp = + rewriter.create(loc, cmp_sub_distance, true /* else=1 */); + rewriter.setInsertionPointToStart(thirdIfOp.thenBlock()); + Value current_index_to_ui_2 = rewriter.create( + loc, rewriter.getIntegerType(32), current_index); + Value current_index_to_f64_2 = rewriter.create( + loc, rewriter.getF64Type(), current_index_to_ui_2); + rewriter.create(loc, current_index_to_f64_2, alloc_output, + ValueRange{peaks_count}); + auto peaks_count_inc_2 = + rewriter.create(loc, peaks_count, cst_idx_one); + rewriter.create(loc, peaks_count_inc_2, alloc_peaks_count, + ValueRange{}); + + rewriter.setInsertionPointAfter(forOp1); + // debug + // forOp1->dump(); + + /* Setting last element of the output as the count of peaks. */ + auto peaks_count_final = rewriter.create( + loc, alloc_peaks_count, ValueRange{}); + // index to f64 + Value peaks_count_final_to_ui = rewriter.create( + loc, rewriter.getIntegerType(32), peaks_count_final); + Value peaks_count_final_to_f64 = rewriter.create( + loc, rewriter.getF64Type(), peaks_count_final_to_ui); + + Value result_size = rewriter.create( + loc, rewriter.getIndexType(), + rewriter.getIndexAttr(tensorType.getShape()[0])); + + rewriter.create(loc, peaks_count_final_to_f64, alloc_output, + addMapForCurrent, ValueRange{result_size}); + + // auto testValue = rewriter.create( + // loc, alloc, ValueRange{cst_idx_zero}); + + // rewriter.create(loc, testValue, alloc_output, + // addMapForCurrent, ValueRange{result_size}); + + rewriter.replaceOp(op, alloc_output); + + return success(); + } +}; + //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: Unary operations //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index 881880a1f4e1..6db877eb811a 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -744,6 +744,19 @@ class MLIRGenImpl { operands[1]); } + // Diff2MeanOptimized Op + if (callee == "lms2findPeaks") { + if (call.getArgs().size() != 6) { + emitError(location, + "MLIR codegen encountered an error: dsp.lmsFilterResponse2findPeaks " + "accepts only 6 arguments"); + return nullptr; + } + return builder.create(location, operands[0], + operands[1], operands[2], operands[3], operands[4], operands[5]); + } + + // Set Elem At Indx if (callee == "setElemAtIndx") { if (call.getArgs().size() != 3) { diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp index 8fcabe9faea6..de6aa933700f 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp @@ -237,6 +237,47 @@ struct SimplifyDiff2Mean : public mlir::OpRewritePattern { } }; +struct SimplifyLMS2FindPeaks : public mlir::OpRewritePattern { + // + SimplifyLMS2FindPeaks(mlir::MLIRContext *context) + : OpRewritePattern(context, 1) {} + + mlir::LogicalResult + matchAndRewrite(FindPeaksOp op, + mlir::PatternRewriter &rewriter) const override { + // + mlir::Value findPeaksOp_operand0 = op.getOperand(0); + + // check if this is coming from diff operation. + LMSFilterResponseOp prev_lmsFilterResponseOp = + findPeaksOp_operand0.getDefiningOp(); + + if (!prev_lmsFilterResponseOp) + return failure(); + + mlir::Value findPeaksOp_operand1 = op.getOperand(1); + mlir::Value findPeaksOp_operand2 = op.getOperand(2); + mlir::Value prev_lmsFilterResponseOp_operand0 = + prev_lmsFilterResponseOp.getOperand(0); + mlir::Value prev_lmsFilterResponseOp_operand1 = + prev_lmsFilterResponseOp.getOperand(1); + mlir::Value prev_lmsFilterResponseOp_operand2 = + prev_lmsFilterResponseOp.getOperand(2); + mlir::Value prev_lmsFilterResponseOp_operand3 = + prev_lmsFilterResponseOp.getOperand(3); + + auto optimizedOp = rewriter.create( + op.getLoc(), prev_lmsFilterResponseOp_operand0, + prev_lmsFilterResponseOp_operand1, prev_lmsFilterResponseOp_operand2, + prev_lmsFilterResponseOp_operand3, findPeaksOp_operand1, + findPeaksOp_operand2); + + // Repalce the use of original diff operation with this operation + rewriter.replaceOp(op, optimizedOp.getResult()); + return mlir::success(); + } +}; + struct SimplifyBack2BackDelay : public mlir::OpRewritePattern { // SimplifyBack2BackDelay(mlir::MLIRContext *context) @@ -591,8 +632,7 @@ struct SimplifyFFTRealAtInputRealSymm : public OpRewritePattern { SimplifyFFTRealAtInputRealSymm(MLIRContext *context) : OpRewritePattern(context, /*benefit=*/1) {} - LogicalResult matchAndRewrite(FFT1DRealOp Op, - PatternRewriter &rewriter) const override { + LogicalResult matchAndRewrite(FFT1DRealOp Op,PatternRewriter &rewriter) const override { // Check if there is a corresponding FFT1DImgOp with the same input. mlir::Value fftOperand_input = Op.getInput(); dsp::FIRFilterYSymmOptimizedOp op_FIRFilterYSymmOptimizedOp = @@ -607,8 +647,8 @@ struct SimplifyFFTRealAtInputRealSymm : public OpRewritePattern { auto fft1dRealSymmOp = rewriter.create(Op.getLoc(), Op.getInput()); DEBUG_PRINT_NO_ARGS(); - rewriter.replaceOp(Op, fft1dRealSymmOp.getResult()); - // rewriter.replaceOp(Op, fft1dRealSymmOp); + // rewriter.replaceOp(Op, fft1dRealSymmOp.getResult()); + rewriter.replaceOp(Op, fft1dRealSymmOp); DEBUG_PRINT_NO_ARGS(); return success(); } @@ -621,8 +661,7 @@ struct SimplifyFFTImgAtInputRealSymm : public OpRewritePattern { SimplifyFFTImgAtInputRealSymm(MLIRContext *context) : OpRewritePattern(context, /*benefit=*/1) {} - LogicalResult matchAndRewrite(FFT1DImgOp Op, - PatternRewriter &rewriter) const override { + LogicalResult matchAndRewrite(FFT1DImgOp Op, PatternRewriter &rewriter) const override { // Check if there is a corresponding FFT1DImgOp with the same input. mlir::Value fftOperand_input = Op.getInput(); dsp::FIRFilterYSymmOptimizedOp op_FIRFilterYSymmOptimizedOp = @@ -690,33 +729,37 @@ struct SimplifyLMSFilterResponsewithGain } }; -struct SimplifySpaceModDemodulate : public mlir::OpRewritePattern { - SimplifySpaceModDemodulate(mlir::MLIRContext *context) : OpRewritePattern(context, 1) {} - - mlir::LogicalResult - matchAndRewrite(SpaceDemodulateOp op, mlir::PatternRewriter &rewriter) const override { - - // a flag checking if the define operation chain of demod op contains mod op - bool opt = false; - SpaceModulateOp prev_mod; - auto iter = op.getOperand(); - while(iter.getDefiningOp()) { - auto pred = iter.getDefiningOp(); - // llvm::errs() << pred->getName().getStringRef() << "\n"; - if(llvm::dyn_cast(*pred)) { - opt = true; - prev_mod = llvm::dyn_cast(*pred); - break; - } - iter = (*pred).getOperand(0); - } - - if(!opt) return failure(); - - auto constVal = prev_mod.getOperand().getDefiningOp(); - rewriter.replaceOp(op, constVal); - return mlir::success(); - } +struct SimplifySpaceModDemodulate + : public mlir::OpRewritePattern { + SimplifySpaceModDemodulate(mlir::MLIRContext *context) + : OpRewritePattern(context, 1) {} + + mlir::LogicalResult + matchAndRewrite(SpaceDemodulateOp op, + mlir::PatternRewriter &rewriter) const override { + + // a flag checking if the define operation chain of demod op contains mod op + bool opt = false; + SpaceModulateOp prev_mod; + auto iter = op.getOperand(); + while (iter.getDefiningOp()) { + auto pred = iter.getDefiningOp(); + // llvm::errs() << pred->getName().getStringRef() << "\n"; + if (llvm::dyn_cast(*pred)) { + opt = true; + prev_mod = llvm::dyn_cast(*pred); + break; + } + iter = (*pred).getOperand(0); + } + + if (!opt) + return failure(); + + auto constVal = prev_mod.getOperand().getDefiningOp(); + rewriter.replaceOp(op, constVal); + return mlir::success(); + } }; // =================================== @@ -734,15 +777,14 @@ struct SimplifySpaceModDemodulate : public mlir::OpRewritePattern(context); + results.add(context); } } void FFT1DRealOp::getCanonicalizationPatterns(RewritePatternSet &results, MLIRContext *context) { if (getEnableCanonicalOpt()) { - results.add< // SimplifyFFTRealAndImg, + results.add(context); } } @@ -812,19 +854,24 @@ void MeanOp::getCanonicalizationPatterns(RewritePatternSet &results, } } +void FindPeaksOp::getCanonicalizationPatterns(RewritePatternSet &results, + MLIRContext *context) { + if (getEnableCanonicalOpt()) { + results.add(context); + } +} + /// Register our patterns as "canonicalization" patterns on the ReshapeOp so /// that they can be picked up by the Canonicalization framework. -void ReshapeOp::getCanonicalizationPatterns(RewritePatternSet &results, - MLIRContext *context) { +void ReshapeOp::getCanonicalizationPatterns(RewritePatternSet &results, MLIRContext *context) { if (getEnableCanonicalOpt()) { results.add(context); } } -void SpaceDemodulateOp::getCanonicalizationPatterns(RewritePatternSet &results, - MLIRContext *context) { - if(getEnableCanonicalOpt()) { - results.add(context); - } -} +void SpaceDemodulateOp::getCanonicalizationPatterns(RewritePatternSet &results, MLIRContext *context) { + if (getEnableCanonicalOpt()) { + results.add(context); + } +} From 141ff24234e51c9d44b345ffe150514b458ac217 Mon Sep 17 00:00:00 2001 From: "Kuo, Mei-Chun" <94007620+Megan0704-1@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:51:39 -0700 Subject: [PATCH 26/45] Normalize & opt (#30) * normalizeOp * add to noise cancel and echo cancel application * normalize fir filter opt --- .../BenchmarkTest/DSP-DSL/echocancelling.py | 9 +- .../BenchmarkTest/DSP-DSL/noisecancelling.py | 9 +- .../dsp/SimpleBlocks/include/toy/Ops.td | 41 +++ .../dsp/SimpleBlocks/mlir/Dialect.cpp | 32 +++ .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 233 +++++++++++++++++- .../dsp/SimpleBlocks/mlir/MLIRGen.cpp | 22 +- .../dsp/SimpleBlocks/mlir/ToyCombine.cpp | 47 +++- 7 files changed, 372 insertions(+), 21 deletions(-) diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/echocancelling.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/echocancelling.py index 3d85df2a79e4..3b41b87fd4ac 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/echocancelling.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/echocancelling.py @@ -1,8 +1,8 @@ def main() { var fs = 8000; - # var step = 1/8000; + # var step = 1/8000; # print(step); - var input = getRangeOfVector(0, 100000000, 1); + var input = getRangeOfVector(0, 10000, 0.000125); var f_sig = 500; var pi = 3.14159265359; var getMultiplier = 2 * pi * f_sig; @@ -21,6 +21,7 @@ def main() { var mu = 0.01; var filterSize = 32; var y = lmsFilterResponse(noisy_sig, clean_sig, mu, filterSize); - print(y); + var z = normalize(y); + print(z); -} \ No newline at end of file +} diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/noisecancelling.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/noisecancelling.py index 181b9d823a49..a6983865a48c 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/noisecancelling.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/noisecancelling.py @@ -1,8 +1,8 @@ def main() { var fs = 8000; - # var step = 1/8000; + # var step = 1/8000; # print(step); - var input = getRangeOfVector(0, 100000000, 1); + var input = getRangeOfVector(0, 10000, 0.000125); var f_sig = 500; var pi = 3.14159265359; var getMultiplier = 2 * pi * f_sig; @@ -19,10 +19,11 @@ def main() { var noisy_sig = clean_sig + noise1; # print(noisy_sig); - # print(clean_sig); var mu = 0.01; var filterSize = 32; var y = lmsFilterResponse(noisy_sig, clean_sig, mu, filterSize); - print(y); + # var a = norm_LMSFilterResponse_opt(noisy_sig, clean_sig, mu, filterSize); + var z = normalize(y); + print(z); } diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 56c982e7b280..3c61bbb00f1c 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -2568,8 +2568,49 @@ def ArgMaxOp : Dsp_Op<"argmax", [Pure , DeclareOpInterfaceMethods ]; } +//===----------------------------------------------------------------------===// +// NormalizeOp +//===----------------------------------------------------------------------===// + +def NormalizeOp : Dsp_Op<"normalize", [Pure , DeclareOpInterfaceMethods]> { + let summary = "normalize operation."; + let description = [{ + normalization dsp operation. + }]; + + let arguments = (ins F64Tensor:$signal); + let results = (outs F64Tensor); + + let builders = [ + OpBuilder<(ins "Value":$signal)> + ]; + + let hasCanonicalizer = 1; + } + +//===----------------------------------------------------------------------===// +// NormLMSFilterResponseOptimizeOp +//===----------------------------------------------------------------------===// +def NormLMSFilterResponseOptimizeOp : Dsp_Op<"norm_LMSFilterResponse_opt", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "LMS filter Response + norm optimize"; + let description = [{ + norm + lmsfilter + }]; + + let arguments = (ins F64Tensor:$lhs, F64Tensor:$rhs, F64Tensor:$mu, F64Tensor:$filterLen); + + let results = (outs F64Tensor); + + let builders = [ + OpBuilder<(ins "Value":$lhs, "Value":$rhs, "Value":$mu, "Value":$filterLen)> + ]; + + let hasVerifier = 1; +} + #endif // TOY_OPS diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp index e578e10e42c2..d14135e187ae 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/Dialect.cpp @@ -3276,6 +3276,38 @@ mlir::LogicalResult SpaceErrCorrectionOp::verify() { return mlir::success(); } +//===----------------------------------------------------------------------===// +// NormalizeOp +//===----------------------------------------------------------------------===// + +void NormalizeOp::build(mlir::OpBuilder &builder, mlir::OperationState &state, mlir::Value signal) { + state.addTypes({UnrankedTensorType::get(builder.getF64Type())}); + state.addOperands({signal}); +} + +void NormalizeOp::inferShapes() { getResult().setType(getSignal().getType()); } + +//===----------------------------------------------------------------------===// +// NormLMSFilterResponseOptimizeOp +//===----------------------------------------------------------------------===// + +void NormLMSFilterResponseOptimizeOp::build(mlir::OpBuilder &builder, + mlir::OperationState &state, mlir::Value lhs, + mlir::Value rhs, mlir::Value mu, + mlir::Value filterLen) { + + state.addTypes(UnrankedTensorType::get(builder.getF64Type())); + state.addOperands({lhs, rhs, mu, filterLen}); +} + +void NormLMSFilterResponseOptimizeOp::inferShapes() { + getResult().setType(getLhs().getType()); +} + +mlir::LogicalResult NormLMSFilterResponseOptimizeOp::verify() { + return mlir::success(); +} + //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index bd5e7ccabc54..253171c8f833 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -9008,7 +9008,6 @@ struct QamDemodulateOpLowering : public ConversionPattern { } }; // qam_demodulate op -//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// // ToyToAffine RewritePatterns: BeamForm operations //===----------------------------------------------------------------------===// @@ -9481,8 +9480,226 @@ struct PowOpLowering : public ConversionPattern { } }; -} // namespace +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: Normalize operations +//===----------------------------------------------------------------------===// + +struct NormalizeOpLowering : public ConversionPattern { + NormalizeOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::NormalizeOp::getOperationName(), 1, ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + auto tensorType = llvm::dyn_cast(*op->result_type_begin()); + auto memRefType = convertTensorToMemRef(tensorType); + auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); + auto shape = tensorType.getShape()[0]; + + dsp::NormalizeOpAdaptor adaptor(operands); + Value signal = adaptor.getSignal(); + + Value min = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(INT64_MAX)); + Value max = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(INT64_MIN)); + + int64_t lb=0, ub=shape, step=1; + // finding min and max; + affine::AffineForOp forOp = rewriter.create(loc, lb, ub, step, ValueRange{min, max}); + auto iv = forOp.getInductionVar(); + rewriter.setInsertionPointToStart(forOp.getBody()); + + auto minVal = forOp.getBody()->getArgument(1); + auto maxVal = forOp.getBody()->getArgument(2); + + auto cmpVal = rewriter.create(loc, signal, ValueRange{iv}); + Value isMin = rewriter.create(loc, arith::CmpFPredicate::OLT, cmpVal, minVal); + Value isMax = rewriter.create(loc, arith::CmpFPredicate::OGT, cmpVal, maxVal); + + auto minOut = rewriter.create(loc, isMin, cmpVal, minVal); + auto maxOut = rewriter.create(loc, isMax, cmpVal, maxVal); + + rewriter.create(loc, ValueRange{minOut.getResult(), maxOut.getResult()}); + rewriter.setInsertionPointAfter(forOp); + + auto minSignal = forOp.getResults()[0]; + auto maxSignal = forOp.getResults()[1]; + + auto dividend = rewriter.create(loc, maxSignal, minSignal); + // ele-wise normalize + affine::AffineForOp forOpI = rewriter.create(loc, lb, ub, step); + auto ivI = forOpI.getInductionVar(); + rewriter.setInsertionPointToStart(forOpI.getBody()); + + auto loadedVal = rewriter.create(loc, signal, ValueRange{ivI}); + auto subVal = rewriter.create(loc, loadedVal, minSignal); + auto resultVal = rewriter.create(loc, subVal, dividend); + + rewriter.create(loc, resultVal, alloc, ValueRange{ivI}); + rewriter.setInsertionPointAfter(forOpI); + + rewriter.replaceOp(op, alloc); + return mlir::success(); + } +}; + +//===----------------------------------------------------------------------===// +// ToyToAffine RewritePatterns: NormLMSFilterResponseOptimizeOp operations +//===----------------------------------------------------------------------===// + +struct NormLMSFilterResponseOptimizeOpLowering : public ConversionPattern { + NormLMSFilterResponseOptimizeOpLowering(MLIRContext *ctx) + : ConversionPattern(dsp::NormLMSFilterResponseOptimizeOp::getOperationName(), 1, + ctx) {} + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const final { + auto loc = op->getLoc(); + + auto tensorType = llvm::cast((*op->result_type_begin())); + + // allocation & deallocation for the result of this operation + auto memRefType = convertTensorToMemRef(tensorType); + auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter); + + // construct affine loops for the input + SmallVector lowerBounds(tensorType.getRank(), /*Value*/ 0); + SmallVector steps(tensorType.getRank(), /*Value=*/1); + + LMSFilterOpAdaptor lmsFilterAdaptor(operands); + + Value zeroval = rewriter.create( + loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(0)); + Value mu = rewriter.create(loc, lmsFilterAdaptor.getMu()); + + // For loop -- iterate from 0 to last + int64_t lb = 0; + int64_t numSamples = tensorType.getShape()[0]; + int64_t step = 1; + + Value GetFilterLOp = op->getOperand(3); + dsp::ConstantOp constantOp3rdArg = + GetFilterLOp.getDefiningOp(); + DenseElementsAttr constant3rdValue = constantOp3rdArg.getValue(); + + auto elements1 = constant3rdValue.getValues(); + float filterlenval = elements1[0].getValueAsDouble(); + auto FilterLength = (uint64_t)filterlenval; + + auto yMemRefType = MemRefType::get({numSamples}, rewriter.getF64Type()); + auto wAlloc = rewriter.create(loc, yMemRefType); + + Value min = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(INT64_MAX)); + Value max = rewriter.create(loc, rewriter.getF64Type(), rewriter.getF64FloatAttr(INT64_MIN)); + + affine::AffineForOp forOp1 = + rewriter.create(loc, lb, numSamples, step, ValueRange{min, max}); + auto iv = forOp1.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp1.getBody()); + + AffineExpr d0, d1, s0; + bindDims(rewriter.getContext(), d0, d1); + AffineExpr ExprForXSlice = d0 - d1; + AffineMap addMapForLMSFilter = AffineMap::get(2, 0, ExprForXSlice); + IntegerSet set1 = IntegerSet::get(2, 0, {ExprForXSlice}, {false}); + + + rewriter.create(loc, zeroval, alloc, ValueRange{iv}); + + affine::AffineForOp forOp2 = + rewriter.create(loc, lb, FilterLength, step); + auto iv2 = forOp2.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp2.getBody()); + + auto ifOp = rewriter.create( + loc, set1, ValueRange{iv, iv2}, false /*no else*/); + rewriter.setInsertionPointToStart(ifOp.getThenBlock()); + + Value inputX = + rewriter.create(loc, lmsFilterAdaptor.getLhs(), + addMapForLMSFilter, ValueRange{iv, iv2}); + Value w = rewriter.create(loc, wAlloc, + ValueRange{iv2}); // memRefType + + auto wmulx = rewriter.create(loc, inputX, w); + auto ybefore = rewriter.create(loc, alloc, ValueRange{iv}); + auto sumNext = rewriter.create(loc, wmulx, ybefore); + rewriter.create(loc, sumNext, alloc, ValueRange{iv}); + rewriter.setInsertionPointAfter(ifOp); + rewriter.setInsertionPointAfter(forOp2); + + auto cmpVal = rewriter.create(loc, alloc, ValueRange{iv}); + Value minVal = forOp1.getBody()->getArgument(1); + Value maxVal = forOp1.getBody()->getArgument(2); + + auto minOut = rewriter.create(loc, cmpVal, minVal); + auto maxOut = rewriter.create(loc, cmpVal, maxVal); + // get e[n] = d[n] - y[n] + + Value desiredX = rewriter.create( + loc, lmsFilterAdaptor.getRhs(), ValueRange{iv}); + Value ynew = rewriter.create(loc, alloc, ValueRange{iv}); + + Value err = rewriter.create(loc, desiredX, ynew); + + affine::AffineForOp forOp3 = + rewriter.create(loc, lb, FilterLength, step); + auto iv3 = forOp3.getInductionVar(); + + rewriter.setInsertionPointToStart(forOp3.getBody()); + + auto ifOp2 = rewriter.create( + loc, set1, ValueRange{iv, iv3}, false /*no else*/); + rewriter.setInsertionPointToStart(ifOp2.getThenBlock()); + + Value inputX2 = + rewriter.create(loc, lmsFilterAdaptor.getLhs(), + addMapForLMSFilter, ValueRange{iv, iv3}); + + Value Prevw2 = rewriter.create(loc, wAlloc, ValueRange{iv3}); + + // f(u(n),e(n),μ)=μe(n)u∗(n) + Value mul1 = rewriter.create(loc, err, inputX2); + Value mul2 = rewriter.create(loc, mu, mul1); + + // FInal w[n] + Value answer = rewriter.create(loc, Prevw2, mul2); + + rewriter.create(loc, answer, wAlloc, ValueRange{iv3}); + rewriter.setInsertionPointAfter(ifOp2); + rewriter.setInsertionPointAfter(forOp3); + + rewriter.create(loc, ValueRange{minOut.getResult(), maxOut.getResult()}); + rewriter.setInsertionPointAfter(forOp1); + + Value minSignal = forOp1.getResults()[0]; + Value maxSignal = forOp1.getResults()[1]; + + Value dividend = rewriter.create(loc, maxSignal, minSignal); + + // ele-wise normalize + affine::AffineForOp forOpI = rewriter.create(loc, lb, numSamples, step); + auto ivI = forOpI.getInductionVar(); + rewriter.setInsertionPointToStart(forOpI.getBody()); + + auto loadedVal = rewriter.create(loc, alloc, ValueRange{ivI}); + auto subVal = rewriter.create(loc, loadedVal, minSignal); + auto resultVal = rewriter.create(loc, subVal, dividend); + + rewriter.create(loc, resultVal, alloc, ValueRange{ivI}); + rewriter.setInsertionPointAfter(forOpI); + + rewriter.replaceOp(op, alloc); + + return success(); + } +}; + +}// namespace //===----------------------------------------------------------------------===// // ToyToAffineLoweringPass //===----------------------------------------------------------------------===// @@ -9554,12 +9771,12 @@ void ToyToAffineLoweringPass::runOnOperation() { FFT1DImgConjSymmOpLowering, FFTRealOpLowering, FFTImagOpLowering, Conv2DOpLowering, ShiftRightOpLowering, MatmulOpLowering, ThresholdUpOpLowering, QamModulateRealOpLowering, - QamModulateImgOpLowering, QamDemodulateOpLowering, BeamFormOpLowering, - SpaceModulateOpLowering, SpaceDemodulateOpLowering, - SpaceErrCorrectionOpLowering, MedianFilterOpLowering, FindPeaksOpLowering, - MaxOpLowering, MeanOpLowering, DiffOpLowering, AbsOpLowering, - ArgMaxOpLowering, GetSingleElemAtIdxOpLowering, - Diff2MeanOptimizedOpLowering>(&getContext()); + QamModulateImgOpLowering, QamDemodulateOpLowering, FindPeaksOpLowering, + BeamFormOpLowering, SpaceModulateOpLowering, SpaceDemodulateOpLowering, + SpaceErrCorrectionOpLowering, FindPeaksOpLowering, MaxOpLowering, + MeanOpLowering, DiffOpLowering, GetSingleElemAtIdxOpLowering, Diff2MeanOptimizedOpLowering, + NormalizeOpLowering, NormLMSFilterResponseOptimizeOpLowering>( + &getContext()); // With the target and rewrite patterns defined, we can now attempt the // conversion. The conversion will signal failure if any of our `illegal` diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp index 6db877eb811a..d5c791be183c 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/MLIRGen.cpp @@ -462,7 +462,7 @@ class MLIRGenImpl { } return builder.create(location, operands[0], operands[1]); } - + // Abs Op if(callee == "abs") { if (call.getArgs().size() != 1) { @@ -488,6 +488,26 @@ class MLIRGenImpl { return builder.create(location, operands[0], axis); } + // Normalize Op + if (callee == "normalize") { + if (call.getArgs().size() != 1) { + emitError(location, "MLIR codegen encountered an error: dsp.normalize " + "accepts only 1 arguments: input tensor"); + return nullptr; + } + return builder.create(location, operands[0]); + } + + // Normalize LMS filter Op + if (callee == "norm_LMSFilterResponse_opt") { + if (call.getArgs().size() != 4) { + emitError(location, "MLIR codegen encountered an error: dsp.norm_LMSFilterResponse_opt " + "accepts 4 arguments "); + return nullptr; + } + return builder.create(location, operands[0], operands[1], operands[2], operands[3]); + } + // Shift right Op if (callee == "shiftRight") { if (call.getArgs().size() != 2) { diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp index de6aa933700f..980a91fa4c1d 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp @@ -762,6 +762,38 @@ struct SimplifySpaceModDemodulate } }; +struct SimplifyNormLMSFilterResponse : public mlir::OpRewritePattern { + SimplifyNormLMSFilterResponse(mlir::MLIRContext *ctx) : OpRewritePattern(ctx, 1) {} + + mlir::LogicalResult + matchAndRewrite(NormalizeOp op, mlir::PatternRewriter &rewriter) const override { + bool opt = false; + + Value signal = op.getOperand(); + Operation* signalOp = signal.getDefiningOp(); + Operation* filterOp = llvm::dyn_cast(signalOp); + + if(filterOp) opt = true; + + if(!opt) return failure(); + + Value filterOp_operand0 = filterOp->getOperand(0); + Value filterOp_operand1 = filterOp->getOperand(1); + Value filterOp_operand2 = filterOp->getOperand(2); + Value filterOp_operand3 = filterOp->getOperand(3); + + auto normLMSfilterOpt = rewriter.create ( + op.getLoc(), filterOp_operand0, filterOp_operand1, filterOp_operand2, filterOp_operand3 + ); + + rewriter.replaceOp(op, normLMSfilterOpt); + if(filterOp->use_empty()) { + rewriter.eraseOp(filterOp); + } + + return mlir::success(); + } +}; // =================================== // =================================== // =================================== @@ -870,8 +902,15 @@ void ReshapeOp::getCanonicalizationPatterns(RewritePatternSet &results, MLIRCont } } -void SpaceDemodulateOp::getCanonicalizationPatterns(RewritePatternSet &results, MLIRContext *context) { - if (getEnableCanonicalOpt()) { - results.add(context); - } +void SpaceDemodulateOp::getCanonicalizationPatterns(RewritePatternSet &results, + MLIRContext *context) { + if(getEnableCanonicalOpt()) { + results.add(context); + } +} + +void NormalizeOp::getCanonicalizationPatterns(RewritePatternSet &results, MLIRContext *ctx) { + if(getEnableCanonicalOpt()) { + results.add(ctx); + } } From 76b1b2a11468476c22ebb99e5c48560e0ffeeb5c Mon Sep 17 00:00:00 2001 From: "Kuo, Mei-Chun" <94007620+Megan0704-1@users.noreply.github.com> Date: Fri, 8 Nov 2024 20:39:33 -0700 Subject: [PATCH 27/45] parsevaals opt (#31) --- .../BenchmarkTest/DSP-DSL/energyOfSignal.py | 25 +++---- .../BenchmarkTest/DSP-DSL/opt_energy.py | 8 +++ .../dsp/SimpleBlocks/include/toy/Ops.td | 2 + .../SimpleBlocks/mlir/LowerToAffineLoops.cpp | 8 +-- .../dsp/SimpleBlocks/mlir/ToyCombine.cpp | 70 +++++++++++++++++-- 5 files changed, 86 insertions(+), 27 deletions(-) create mode 100644 mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/opt_energy.py diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/energyOfSignal.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/energyOfSignal.py index 9f17b3ef81da..fc4c41f8b8dc 100644 --- a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/energyOfSignal.py +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/energyOfSignal.py @@ -1,21 +1,12 @@ - def main() { - var input = getRangeOfVector(0, 40000, 1); - #calculate x[l] - - - # var fft1 = fft1d(input); - #calculate fft : fft1 = fft(conv1) - var fft_real = fft1dreal(input); - var fft_img = fft1dimg(input); - var sq_abs = square(fft_real) + square(fft_img) ; - # var sq_abs = square(fft1); - # sum = sum(sq_abs) - var sum1 = sum(sq_abs); - # res = gain(sum , 1/N) - var len1 = len(input); - var res = sum1 / len1; - print(res); + var input = getRangeOfVector(0, 40000, 1); + var fft_real = fft1dreal(input); + var fft_img = fft1dimg(input); + var sq_abs = square(fft_real) + square(fft_img) ; + var sum1 = sum(sq_abs); + var len1 = len(input); + var res = sum1 / len1; + print(res); } diff --git a/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/opt_energy.py b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/opt_energy.py new file mode 100644 index 000000000000..53be52e48b79 --- /dev/null +++ b/mlir/examples/dsp/SimpleBlocks/Output/TryDSPApps/BenchmarkTest/DSP-DSL/opt_energy.py @@ -0,0 +1,8 @@ +def main() { + + var input = getRangeOfVector(0, 40000, 1); + var sq_abs = square(input); + var sum1 = sum(sq_abs); + print(sum1); +} + diff --git a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td index 3c61bbb00f1c..f86a111df55f 100644 --- a/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td +++ b/mlir/examples/dsp/SimpleBlocks/include/toy/Ops.td @@ -304,6 +304,8 @@ def DivOp : Dsp_Op<"div", let builders = [ OpBuilder<(ins "Value":$lhs, "Value":$rhs)> ]; + + let hasCanonicalizer = 1; } //===----------------------------------------------------------------------===// diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp index 253171c8f833..18994ce875fc 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/LowerToAffineLoops.cpp @@ -9526,7 +9526,7 @@ struct NormalizeOpLowering : public ConversionPattern { auto minSignal = forOp.getResults()[0]; auto maxSignal = forOp.getResults()[1]; - auto dividend = rewriter.create(loc, maxSignal, minSignal); + auto divisor = rewriter.create(loc, maxSignal, minSignal); // ele-wise normalize affine::AffineForOp forOpI = rewriter.create(loc, lb, ub, step); auto ivI = forOpI.getInductionVar(); @@ -9534,7 +9534,7 @@ struct NormalizeOpLowering : public ConversionPattern { auto loadedVal = rewriter.create(loc, signal, ValueRange{ivI}); auto subVal = rewriter.create(loc, loadedVal, minSignal); - auto resultVal = rewriter.create(loc, subVal, dividend); + auto resultVal = rewriter.create(loc, subVal, divisor); rewriter.create(loc, resultVal, alloc, ValueRange{ivI}); rewriter.setInsertionPointAfter(forOpI); @@ -9679,7 +9679,7 @@ struct NormLMSFilterResponseOptimizeOpLowering : public ConversionPattern { Value minSignal = forOp1.getResults()[0]; Value maxSignal = forOp1.getResults()[1]; - Value dividend = rewriter.create(loc, maxSignal, minSignal); + Value divisor = rewriter.create(loc, maxSignal, minSignal); // ele-wise normalize affine::AffineForOp forOpI = rewriter.create(loc, lb, numSamples, step); @@ -9688,7 +9688,7 @@ struct NormLMSFilterResponseOptimizeOpLowering : public ConversionPattern { auto loadedVal = rewriter.create(loc, alloc, ValueRange{ivI}); auto subVal = rewriter.create(loc, loadedVal, minSignal); - auto resultVal = rewriter.create(loc, subVal, dividend); + auto resultVal = rewriter.create(loc, subVal, divisor); rewriter.create(loc, resultVal, alloc, ValueRange{ivI}); rewriter.setInsertionPointAfter(forOpI); diff --git a/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp b/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp index 980a91fa4c1d..b775c071d1b5 100644 --- a/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp +++ b/mlir/examples/dsp/SimpleBlocks/mlir/ToyCombine.cpp @@ -540,6 +540,7 @@ struct SimplifyFIRFilterRespnseWithSymmFilter } }; +// label: pass 1st // Pseudo code: // if the FFT1DRealOp & FFT1DImgOp has same input then replace them with single // %4 = "dsp.fft1dreal"(%3) : (tensor<10xf64>) -> tensor<10xf64> @@ -767,15 +768,11 @@ struct SimplifyNormLMSFilterResponse : public mlir::OpRewritePattern(signalOp); + Operation* filterOp = signal.getDefiningOp(); - if(filterOp) opt = true; - - if(!opt) return failure(); + if(!filterOp) return failure(); Value filterOp_operand0 = filterOp->getOperand(0); Value filterOp_operand1 = filterOp->getOperand(1); @@ -794,6 +791,61 @@ struct SimplifyNormLMSFilterResponse : public mlir::OpRewritePattern { + SimplifyDSSDPass(mlir::MLIRContext *ctx) : OpRewritePattern(ctx, 1) {} + + mlir::LogicalResult + matchAndRewrite(DivOp op, mlir::PatternRewriter &rewriter) const override { + +#define CHECK(x) if(!x) return failure(); +#define REMOVE(x) if(x->use_empty()) rewriter.eraseOp(x); +#define DEBUG(x) {llvm::errs() << "check for " << x << "\n";} +#define PASS llvm::errs() << "pass\n"; + + auto loc = op.getLoc(); + + // pattern -> CHECK() + Operation* sumOp = op.getOperand(0).getDefiningOp(); + CHECK(sumOp); + + Operation* addOp = sumOp->getOperand(0).getDefiningOp(); + CHECK(addOp); + + Operation* sqrtOp0 = addOp->getOperand(0).getDefiningOp(); + CHECK(sqrtOp0); + + Operation* sqrtOp1 = addOp->getOperand(1).getDefiningOp(); + CHECK(sqrtOp1); + + Operation* fftRealOp = sqrtOp0->getOperand(0).getDefiningOp(); + CHECK(fftRealOp); + + // See defining op: suppose to be fftImg, but modified beforhand by