<figure><img src="image_1.png" /></figure>

HAL
open science

# Flexible front-end for high level synthesis leveraging heterogeneous compilation

Gaëtan Lounes, Robin Gerzaguet, Matthieu Gautier

► To cite this version:

Gaëtan Lounes, Robin Gerzaguet, Matthieu Gautier. Flexible front-end for high level synthesis leveraging heterogeneous compilation. Workshop on Rapid Simulation and Performance Evaluation for Design Optimization: Methods and Tools (RAPIDO), Jan 2025, Barcelona (ES), Spain. hal-04979196

HAL Id: hal-04979196

https://inria.hal.science/hal-04979196v1

Submitted on 6 Mar 2025

HAL is a multi-disciplinary open access
archive for the deposit and dissemination of sci-
entific research documents, whether they are pub-
lished or not. The documents may come from
teaching and research institutions in France or
abroad, or from public or private research centers.

L'archive ouverte pluridisciplinaire HAL, est
destinée au dépôt et à la diffusion de documents
scientifiques de niveau recherche, publiés ou non,
émanant des établissements d'enseignement et de
recherche français ou étrangers, des laboratoires
publics ou privés.

<figure><img src="image_2.png" /></figure>

Distributed under a Creative Commons Attribution 4.0 International License
Flexible front-end for high level synthesis leveraging
heterogeneous compilation

Gaëtan LOUNES

Robin GERZAGUET

Matthieu GAUTIER

gaetan.lounes@iris.fr

robin.gerzaguet@iris.fr

matthieu.gautier@iris.fr

Univ Rennes, CNRS, IRISA

Univ Rennes, CNRS, IRISA

Univ Rennes, CNRS, IRISA

France

France

France

**Abstract**

The increasing usage of Field Programmable Gate Arrays has en-
abled a large range of applications in hardware acceleration. The
introduction of High Level Synthesis (HLS) has significantly simpli-
fied development and exploration of complex applications. However,
HLS still requires strong hardware knowledge and relies on cus-
tom domain specific language to ease the design burden new HLS
toolchains have been introduced using more expressive compiler
framework such as Multi Level Intermediate Representation (MLIR).
In this context, we aim to use the Julia programming language as a
flexible front-end for HLS framework using MLIR, taking advan-
tage of Julia type system, modular infrastructure and rich libraries.
In this paper, we introduce a new compiler that leverages Julia
compiler infrastructure in order to generate MLIR code, facilitat-
ing hardware acceleration. The interest of this approach has been
demonstrated in the context of rapid prototyping and exploration
of type spaces for numerical algorithms.

1 Introduction

Field Programmable Gate Arrays (FPGAs) are a class of configurable
hardware which have proved their efficiency for low latency, high
throughput applications such as signal processing or neural net-
work edge computing [19]. They are configured using Hardware
Description Language (HDL) such as VHDL. However, this form
is not ideal for fast algorithm development and exploration. High
Level Synthesis (HLS) partially fills the gap by converting behav-
ior description using Domain Specific Language (DSL), similar to
classic programming paradigm, to HDL semantics.

Julia programming language [3] fills a similar gap in software
as it simplifies design: Julia aims to produce directly highly per-
formant program from a flexible high level language. To achieve
this goal, the language relies on several principles: expressive type
system, gradual typing and metaprogramming. Especially, Julia
uses an Intermediate Representation (IR) which is generated and
transformed during compilation; and send to a LLVM [15] toolchain
to generate CPU assembly. These principles have been used for
develop back-end for other kind of targets, such as GPU [2] or
TPU [13] but more specialized hardware as FPGA has been little
explored.

This paper presents a new Julia back-end exploiting these prin-
ciples for hardware acceleration and using a new kind of IR from
the Multi Level Intermediate Representation (MLIR) [16] compiler
framework. MLIR is a framework which generalized the concept of
IR while letting creating several IR semantics following different
levels of abstraction and transform them in the same code. This
framework is used to describe complex compiler toolchain for in-
stance such as ScaleHLS [21] which uses the expressiveness and

modularity of MLIR to create a HLS framework capable of doing
automatic hardware annotation. This paper therefore proposes a
complete HLS toolchain from Julia language to HDL description
leveraging MLIR and ScaleHLS, allowing fast prototyping and ad-
vanced kind of Design Space Exploration (DSE).

Section 2 focus on MLIR, and its uses by ScaleHLS. Section 3 describes the Julia main toolchain and the one develop for MLIR. Next, section 4, details a use case of the new compiler. Section 5 concludes this study.

2 Related Works

## 2.1 MLIR

LLVM compilation infrastructure has made it possible to unify back-
ends by offering a modular IR capable of targeting a wide range of
assemblers. However, this form is not ideal for all applications such
as semantics verification [20] or internal compiler representation.
Languages such as Rust [17] or Julia, which use LLVM, introduce
intermediate language representations with higher-level semantics
to manage specific concepts and transformations such as abstract
types or a borrow checker which cannot efficiency be tackled by
LLVM IR level.

MLIR is a recent compiler infrastructure aiming to bring LLVM
back-end ideas of modularity and capacities to higher level IR. MLIR
generalizes the idea of IR by letting compilers designers introducing
several abstraction levels. A classical MLIR toolchain is composed
of several *dialects* defining concept such as *operations* and *types* at
different levels of abstraction, *passes* which apply transformation
on a dialect or between several dialects. Then, a *translation* step
occurs to transform the MLIR code to a suitable form, for instance,
LLVM IR or a Domain Specific Language (DSL).

## 2.2 MLIR front-end

One left question is how to generate MLIR IR. The framework
proposes a builder API to form IR from different dialects. Several
languages as Python or Elixir provide wrappers around this API
to construct MLIR IR. However, this approach can be cumbersome
to describe complex IR using several dialects. Another approach
was introduced by the Polygeist project [18], which developed
a compilation flow connecting MLIR to polyhedral optimization
tools. Specially for this goal, they introduce a C/C++ frontend to
MLIR. The key idea of this frontend is to use the Clang compiler
infrastructure: the compiler front-end emit Clang AST from C/C++
which is then traversed and transformed to generate MLIR from
several standard dialects.
## 2.3 ScaleHLS

ScaleHLS [21] and his successor HIDA [22] are HLS frameworks that use MLIR to define hardware dialect and enabling the generation of HLS programs that outperform hand-tuned expert design [22]. To achieve that goal, a compiler pipeline composed of several abstraction levels and a DSE have been introduced, starting from MLIR standard dialects and ending with a custom Vitis dialect which is translated to synthesizable Vitis HLS C++.

## 2.4 Dynamic with Julia

MLIR is not the only front-end employed in HLS frameworks. For instance, the dynamic scheduling HLS framework Dynamic [14] represents programs using elastic components derived from a Control Flow Graph (CFG). The possibility of using a custom compiler has been explored [4], especially by using the Julia Language typed IR in order to generate a CFG compatible with Dynamic.

# 3 Julia Compilers

Contrary to Polygeist, we propose to link the flexibility of the Julia language and the one provided by MLIR by operating after Julia middle-end.

## 3.1 Julia Language

Julia [3] is a dynamic typed language designed with performance as a keystone. To achieve that goal, the language provides a simple but expressive type system [5].

<figure><img src="image_3.png" /><figcaption>(a) A simplified part of
Julia type system.</figcaption></figure>

```
det(d::Diagonal) = prod det(d, dusa)
det(l::Integer) = 1
#Using Bareiss algorithm
det(A::AbstractMatrix{T}) = det bareiss(A)
#Using LU decomposition
det(A::AbstractMatrix{T}) = det_lu(A)

#Usage of det function
f(m,n)=det(m)+n
f(Diagonal([1,2,3]), 5.0)

#Optimized IR generated for this f call
1 %1 = Base.getfield(2, :diag)::Vector{Int64}
2 %2 = prod det detFunction, %1::Vector{Int64}::Int64
3 %3 = Base.sitofp{Float64, %2}::Float64
4 %4 = Base.add float%3, %3}::Float64
return %4
== Float64
```

<caption>(b) Multidispatch and typed IR generation.</caption>

<caption>**Figure 1: Julia type system and dispatch.**</caption>

Variables in Julia programs have a type defined within a type tree. Unlike in statically typed languages, variables in Julia can have an abstract types, which define general categories that cannot be instantiated, as well as concrete types represent specific, instantiable types. Function calls are not always determined at compile time; instead, they are dynamically managed by Julia runtime system. Figure 1(a) shows a section of Julia type hierarchy, where edges represent subtype relationships, leaf nodes indicate concrete types, while parent nodes represent abstract types. Additionally, Julia types can depend on other types, as in **AbstractMatrix{T)**, where *T* itself is a type.

These features enable robust behavior, especially when combined with multiple dispatch and specialization. A method in Julia can

have multiple argument signatures, and the runtime will always select the most specific match for dispatch. Figure 1(b) demonstrates such concept by defining a determinant function across various object types. For instance, specialized methods are available for several cases, such as integers, diagonal matrices (abstract type), and matrices of integers (AbstractMatrix{<:Integer}), with a general fallback method provided for the AbstractMatrix signature. An example of the compiler representation generated for an f function call is also shown, illustrating the use of specialization in function dispatch.

## 3.2 Standard Toolchain

<figure><img src="image_4.png" /><figcaption>**Figure 2: Julia compiler pipeline and usage for GPU.**</figcaption></figure>

Julia compilation pipeline relies on the LLVM back-end for generating assembly code, similar to other compilers like Clang or Rustc. A major difference is that Julia is a homoiconic language so the whole infrastructure toolchain before LLVM IR is in Julia and available from the runtime. Figure 2 presents the Julia toolchain, firstly the source code is transformed into an Abstract Syntax Tree (AST). This representation is subsequently lowered into an IR, which is used for type inference via an abstract interpreter using a type lattice. Once type information is gathered, the representation is hence into a typed Static Single Assignment (SSA) form, simplifying further analysis and modifications. At this stage, a series of optimization passes is applied, performing tasks such as dead code elimination (DCE), constant propagation and function inlining. After these optimizations, Julia IR is translated into LLVM IR, incorporating specific intrinsic and built-ins integrated. Finally, the LLVM back-end generates assembly code, which is then handled by Julia Just-In-Time (JIT) compiler for execution.

## 3.3 Custom Toolchain: GPU

This compilation pipeline has been adapted for target architecture beyond x86 assembly [2], such as GPUs. The idea behind these compilation pipelines is to reuse Julia existing compilation infrastructure using metaprogramming. While modifying the back-end after the generation of LLVM code to produce specialized LLVM representation as shown in the bottom part of Figure 2. In the case of Nvidia GPUs, the LLVM IR is then converted into PTX (Parallel
Thread Execution) to be executed. An interesting aspect of this approach is the ability to define Julia types that manage GPU-specific abstractions, such as disjoint memory spaces, static sized arrays or synchronization barriers [1]. Moreover, Julia code written for CPU can be partially reused for GPU applications. However, this approach is not suitable for HLS tools, which require high-level directives that could not be easily processed in the back-end.

## 3.4 Dual toolchain

Commercial HLS tools like Vitis HLS have a C simulator to support test bench designs. To enable rapid prototyping, we have chosen to keep this idea of dual compilation flows:

* the standard compiler, responsible for executing programs as shown in Figure 2.

* a new pipeline dedicated to generate MLIR code shown in Figure 3 targeting ScaleHLS.

<figure><img src="image_5.png" /><figcaption>Figure 3: Modified Julia Compiler generating MLIR IR.</figcaption></figure>

### 3.4.1 A new pipeline.
The core idea of this dual-pipeline approach, illustrated in Figure 3 is to introduce MLIR generation just before the LLVM translation step, maximizing the advantages of Julia compiler, particularly its optimization capabilities. This strategy preserves Julia multiple dispatch and type system while leveraging advanced optimizations. Only a subset of Julia language is supported, programs to translate have to be fully typed, without any foreign call or use of Julia runtime such as reflection. Hence, the Julia IR is traversed and transformed into MLIR code, with specific constructs, such as Phi nodes, converted into MLIR functional blocks. To simplify the integration with standard MLIR dialects, a custom Julia MLIR dialect is created. The purpose of this dialect is to simplify translation, especially for type conversion and raising pass. Then, the MLIR stage then proceeds in two main steps:

* a raising pass: Julia IR, being unstructured, loses for loops and if structures during the lowering process. Loops are recovered taking advantage of Julia iterator specificity and using dominator analysis.

* several lowering passes: the remaining Julia operations and types from the custom dialect are converted into their MLIR equivalents, particularly in standard MLIR dialects such as func [8], arith [7], index [9], memref [10], and Structured Control Flow (SCF) [11]. This step is detailed in further details in the rest of this section.

### 3.4.2 Extending type system.
Julia compiler is highly modular, and its architecture makes it possible to introduce new behaviors. A major benefit of this modularity is the ability to extend Julia type system with custom types and use them with abstract methods. For instance, new hardware abstractions can be introduced into the type system and smoothly integrated in the broad Julia ecosystem. An interesting example is the creation of custom array types, such as statically-sized arrays. Julia does not natively support this type, but it can be defined using parametric types and as a subtype of AbstractArray{T}. StaticArray{S,T,N} is an T type array of shape S with a rank of N. This new type behaves similarly to the standard Array but has the additional advantage of static size information, which is known at compile time. Memref is a MLIR dialect used to define data arrays and operations to use them, especially precise array size can be provided. So, this enables a natural mapping during dialect conversions between Julia and MLIR type as shown in Table 1.

<table><caption>Table 1: Mapping between several Julia Types and MLIR Types</caption><thead><tr><td>Julia Type</td><td>MLIR Dialect.Type</td></tr></thead><tbody><tr><td>Integer64</td><td>Builtin.i64</td></tr><tr><td>Float64</td><td>Builtin.f64</td></tr><tr><td>StaticArray{S,T}</td><td>Memref.memref (shape S, type T)</td></tr><tr><td>Index</td><td>Index.Index</td></tr></tbody></table>

### 3.4.3 New kind of intrinics.
While the standard Julia pipeline maps intrinsic function to LLVM operations during translate, the MLIR pipeline instead maps higher Julia methods to MLIR operations. The main argument is that low level implementation are not useful for MLIR usage because they are designed to follow LLVM backend semantics. As shown in Table 2, one maps generic methods to MLIR operation. One benefit of this approach is to prune large part of type inference tree compared to standard compiler by modifying inlining and forcing type inference.

<table><caption>Table 2: Several Intrinics conversions.</caption><thead><tr><td>Julia Method</td><td>Return Type</td><td>MLIR Dialect Operation</td></tr></thead><tbody><tr><td>+(x::T, y::T) where T::Union[Signed, Unsigned]</td><td>T</td><td>Arith.addi</td></tr><tr><td>-(x::T, y::T) where T::Union[Signed, Unsigned]</td><td>T</td><td>Arith.subi</td></tr><tr><td>(-)(x::T, y::T) where [T::BaseBitSigned]</td><td>Bool</td><td>Arith.cmp(slt)</td></tr><tr><td>setindex(s::StaticArray{S,T}, v::T, index::Index...) where S,T</td><td>Nothing</td><td>Memref.store</td></tr></tbody></table>

### 3.4.4 Custom method table.
All translation cases cannot be handled because MLIR and Julia semantics differ in several ways. Julia defines intrinsic which are not directly mappable to MLIR dialect operation. For instance, the negation of an integer is a Julia intrinsic, but this operation does not exist in arith dialect. Additionally, arrays indexing is different: 1-indexed in Julia and 0-indexed in MLIR. We propose to introduce override method using custom method table in the MLIR pipeline to adapt the semantic. Method tables in Julia control dispatch by associating method signatures (symbols and types) with their corresponding methods. The methods presented
<figure><img src="image_6.png" /><figcaption>Figure 4: GEMM compilation to MLIR.</figcaption></figure>

in Listing 1 resolve the two example cases of semantics gap: unary
negative method is equivalent to a subtraction, and the indexing
shift can be resolved by introducing it explicitly. Moreover, type
must be changed to fit with memref dialect needing MLIR Index
type instead of an integer.

1 @override (-)(a::Number) = 0 - a

2

3 #Index constructor can be mapped to arith.index cast operation

4 struct Index i::Integer end

5 @override Base.getindex(sa::StaticArray{S, T, D}, i::Vararg{Integer,D}) where {S, T, D} =

6 Base.getindex(sa, map((i.) -> Index(i_1), i)...)

<caption>Listing 1: Custom method table usage.</caption>

4 Benchmark on GEMM

To demonstrate the capacities of this compiler to generate usable
MLIR code for ScaleHLS, we consider an example from the Poly-
Bench benchmark suite [12] which is extensively used by HLS
community [21, 22]. General Matrix Multiplication (GEMM), a sim-
ple matrix multiplication defined in Listing 3 and Figure 4, serves as
the example. The objective is to demonstrate that, starting from the
same flexible code base, we can generate hardware architectures
and perform design space exploration using our compiler.

4.1 Example of HLS synthesis from Julia
front-end

First, we present the intermediate compiler steps for GEMM. The
method definition in Julia presented in Figure 4 (i) is abstract. It
means that for the Julia compilation, different methods can be com-
piled based on the signature at the call of the function. Here, it
ensures with the where bound that A, B and C are Matrix of a
subtype of the abstract type **Number**. We suppose next that we
use a concrete specialization while taking 3x3 **Float32** matrix. This

method is compiled using the infrastructure presented before, the
IR showed in Figure 4 (ii) is the final one before MLIR translation.
Specifically, thanks to the specific steps included in the compiler,
a custom method table is used to automatically handle the MLIR
semantics of the memref dialect, which requires the Index type.
Additionally, setindex! and getindex here correspond to MLIR in-
trinsics representing memref.store and memref.load operations. The
resulting MLIR in Figure 4 (iii) is then utilized by ScaleHLS (HIDA)
HLS to generate a synthesizable C++ file compatible with Vitis.
Again, because the compilation steps are automatic and rely on the
high-level syntax defined and illustrated in Figure 4 (i), numerous
function signatures, and thus hardware designs after MLIR genera-
tion can be generated. The proposed tool is therefore valuable for
design space exploration, as demonstrated in the following.

4.2 Type exploration

We propose generating designs for different signatures, specifically
by varying the size of the input data and the type of input data.

1 collected mlir = [] # Placeholder for MLIR outputs

2 function simple_type_exploration()

3 # Exploration on types

4 for type in [Float64, Float32, Int32, Int64]

5 # Exploration on Matrix sizes

6 for i in [3, 5, 7, 9, 10, 13, 15, 20]

7 # Create the matrix with parametric dynamic typing

8 matrix_type = StaticArray((i, 1), type)

9 # Create the unique signature for GEMM call

10 kernel signature = (matrix_type, matrix_type, matrix_type, type, type)

11 # Generates MLIR

12 code = mlir_gen(gemm; signature=kernel signature)

13 # Stores the generated MLIR

14 push!(collected_mlir, code)

15 end

16 end

17 end

<caption>Listing 2: Julia Code for simple type exploration.</caption>
The exploration is shown in Listing 2 and process itself is straight-forward and correspond to a nested loop in Julia that creates the specialized GEMM signature which is used to emit the MLIR code.

In the following, ScaleHLS targets a Pynq-Z1 board and achieves hardware auto-annotations through its DSE capabilities. The following results are generated using HLS synthesis estimation metrics, providing valuable insights into resource utilization and performance. Hardware metrics, such as the number of Flip-Flops (FFs), Look-Up Tables (LUTs), and Digital Signal Processors (DSPs), are employed to evaluate the resource consumption. Additionally, timing metrics, specifically the number of clock cycles required for execution, are analyzed to assess the performance. We represent below two explorations of Listing 2 each with a fixed parameter:

<figure><img src="image_7.png" /><figcaption>Figure 5: GEMM exploration, fixed matrix type Float32.</figcaption></figure>

Figure 5 shows hardware resources and timing metrics of HLS synthesis report for different size N of input of Float32 matrix. For each size, we are able to produce a valid design and observe a general increase in resource usage as the input size grows. A more detailed analysis of the results also reveals the impact of ScaleHLS DSE, which selects a more cycle-efficient design for N=15 at the cost of higher resource consumption. This highlights how exploration using a flexible front-end can help identify edge cases, such as the design for a 15x15 matrix, where trade-offs between performance and resource utilization become evident.

<figure><img src="image_8.png" /><figcaption>Figure 6: GEMM exploration, fixed matrix size 3.</figcaption></figure>

Figure 6 presents the same metrics but varies the type of input data, both in terms of bit width and the nature of operand storage

(e.g., integer and floating-point types). As before, processing blocks can be generated from a parametric description of the types. The results also show a significant increase in the number of clock cycles when switching to floating-point operations. Overall, the flexibility of the proposed compilation flow enables the easy generation of synthesizable blocks, facilitating informed decision-making for the final design implementation.

## 4.3 Comparison to Polygeist

Here, we propose comparing the results of the generated MLIR with C code provided by Polygeist. Listing 3 illustrates the C algorithm while Figure 7 shows the generated MLIR code. Attributes are omitted to enhance readability.

```c
1 #define N 32
2 void gemm(float alpha, float beta, float C[N][N], float A[N][N], float B[N][N]) {
3     for (int i = 0; i < N; i++) {
4         for (int j = 0; j < N; j++) {
5             C[i][j] += beta;
6             for (int k = 0; k < N; k++) {
7                 C[i][j] += alpha * A[i][k] * B[k][j];
8         }
9     }
10 }
11 }
```

<caption>Listing 3: C code from Polygeist for GEMM function.</caption>

```python
module attributes {...} {
    func.func.gemm(%arg0: f32, %arg1: f32, %arg2: memref<32x32xf32>, %arg3: memref<32x32xf32>,
                  %arg4: memref<32x32xf32>) attributes (llvm.linkage = #llvm.linkage<external>) {
        affine.for %arg5 = 0 to 32 {
            %0 = memref.load %arg2[%arg5, %arg6] : memref<32x32xf32>
            %1 = arith.mulf %0, %arg1 : f32
            memref.store %1, %arg2[%arg5, %arg6] : memref<32x32xf32>
        }
        affine.for %arg7 = 0 to 32 {
            %2 = memref.load %arg3[%arg5, %arg7] : memref<32x32xf32>
            %3 = arith.mulf %arg8, %2 : f32
            %4 = memref.load %arg4[%arg7, %arg6] : memref<32x32xf32>
            %5 = arith.mulf %3, %4 : f32
            %6 = memref.load %arg2[%arg5, %arg6] : memref<32x32xf32>
            %7 = arith.addf %6, %5 : f32
            memref.store %7, %arg2[%arg5, %arg6] : memref<32x32xf32>
        }
    }
}
return
}
}
}
```

<caption>Figure 7: generated MLIR from Polygeist.</caption>

The IR shown in Figures 7 and 4 are largely similar, with the main differences stemming from the operation ordering due to the canonicalization pass and the use of the affine dialect [6]. Polygeist incorporates a dedicated pass that converts SCF to the affine dialect, which is used upstream in the ScaleHLS compilation flow. This ensures that both IRs can be processed by ScaleHLS, enabling consistent analysis and optimization. Consequently, developers can seamlessly utilize either approach, benefiting from the integration and flexibility within the compilation toolchain.

We compare in Table 3 the space and time metrics for the 3x3 GEMM operation in Float32 between Polygeist and our approach. The results show that the metrics are very similar, with slight differences explained by a different trade-off chosen by ScaleHLS when optimizing from the similar but distinct MLIR signatures. Specifically, the solution generated by Polygeist demonstrates a slightly lower number of clock cycles but requires greater hardware resources. Again, the key point here is that the Julia signature is
<table><caption>Table 3: Comparison of metrics between Polygeist and Julia-based approach.</caption><thead><tr><td>Metric</td><td>Polygeist</td><td>From Julia</td></tr></thead><tbody><tr><td>Cycles</td><td>31</td><td>35</td></tr><tr><td>DSP</td><td>34</td><td>34</td></tr><tr><td>FF</td><td>4195</td><td>4037</td></tr><tr><td>LUT</td><td>3114</td><td>3032</td></tr></tbody></table>

parametric, whereas the C code provided by Polygeist is statically typed. This flexibility in Julia facilitates more versatile exploration of design space and optimization opportunities.

## 5 Conclusion

In this paper, we introduce a Julia MLIR backend tailored for high-level synthesis, achieved through a modular compiler pipeline that performs IR-to-IR conversion on fully typed Julia programs. This new approach is compared with Polygeist using a reference test bench, highlighting the flexibility to explore type spaces, as demonstrated with simple examples. Our solution employs dual compilation flows: the standard compiler for executing Julia programs, and a new pipeline for generating MLIR code targeting ScaleHLS. The modularity of Julia compiler allows for the extension of the type system with custom types, enhancing the synthesis of hardware designs. Additionally, while the standard Julia pipeline maps intrinsic functions to LLVM operations, our MLIR pipeline translates higher-level Julia methods into MLIR operations, enabling domain-specific optimizations for hardware synthesis. We have tested our approach on the GEMM with different input types and sizes, demonstrating that our solution achieves comparable metrics to state-of-the-art methods while offering greater expressiveness on the front-end. This work demonstrates how integrating Julia with MLIR can streamline the transition from software to hardware design, offering a flexible and efficient workflow for developers.

## References

[1] Tim Besard, Christophe Foket, and Bjorn De Sutter. 2019. Effective Extensible Programming: Unleashing Julia on GPUs. *IEEE Transactions on Parallel and Distributed Systems* 30, 4 (April 2019), 827–841. https://doi.org/10.1109/TPDS.2018.2872064

[2] Tim Besard, Pieter Verstraete, and Bjorn De Sutter. 2016. High-Level GPU Programming in Julia. arXiv:1604.03410 [cs]

[3] Jeff Bezanson, Alan Edelman, Stefan Karpinski, and Viral B. Shah. 2017. Julia: A Fresh Approach to Numerical Computing. *SIAM Rev.* 59, 1 (Jan. 2017), 65–98. https://doi.org/10.1137/141000671

[4] Benjamin Biggs, Ian McInerney, Eric C. Kerrigan, and George A. Constantinides. 2022. High-Level Synthesis Using the Julia Language. arXiv:2201.11522 [cs]

[5] Benjamin Chung. 2023. A Type System for Julia. arXiv:2310.16866 [cs]

[6] MLIR Contributors. 2024. *MLIR Affine Dialect*. https://mlir.llvm.org/docs/Dialects/Affine

[7] MLIR Contributors. 2024. *MLIR Arith Dialect*. https://mlir.llvm.org/docs/Dialects/ArithOps

[8] MLIR Contributors. 2024. *MLIR Func Dialect*. https://mlir.llvm.org/docs/Dialects/Func

[9] MLIR Contributors. 2024. *MLIR Index Dialect*. https://mlir.llvm.org/docs/Dialects/IndexOps

[10] MLIR Contributors. 2024. *MLIR Memref Dialect*. https://mlir.llvm.org/docs/Dialects/MemRef

[11] MLIR Contributors. 2024. *MLIR SCF Dialect*. https://mlir.llvm.org/docs/Dialects/SCFDialect

[12] Polybench Contributors. 2012. *Polybench benchmarks collection*. https://www.cs.colostate.edu/~pouchet/software/polybench/

[13] Keno Fischer and Elliot Saba. 2018. Automatic Full Compilation of Julia Programs and ML Models to Cloud TPUs. arXiv:1810.09868 [cs, stat]

[14] Lana Josipović, Radhika Ghosal, and Paolo Ienne. 2018. Dynamically Scheduled High-level Synthesis. In *ACM/SIGDA International Symposium on Field-Programmable Gate Arrays*. ACM, Monterey CALIFORNIA USA, 127–136. https://doi.org/10.1145/3174243.3174264

[15] Chris Lattner and Vikram Adve. 2005. The LLVM Compiler Framework and Infrastructure Tutorial. In *Languages and Compilers for High Performance Computing (Lecture Notes in Computer Science)*, Rudolf Eigenmann, Zhiyuan Li, and Samuel P. Midkiff (Eds.). Springer, Berlin, Heidelberg, 15–16. https://doi.org/10.1007/11532378_2

[16] Chris Lattner, Mehdi Amini, Uday Bondhugula, Albert Cohen, Andy Davis, Jacques Pienaar, River Riddle, Tatiana Shpeisman, Nicolas Vasilache, and Oleksandr Zinenko. 2020. MLIR: A Compiler Infrastructure for the End of Moore’s Law. arXiv:2002.11054 [cs]

[17] Nicholas D. Matsakis and Felix S. Klock. 2014. The Rust Language. *ACM SIGAda Ada Letters* 34, 3 (Nov. 2014), 103–104. https://doi.org/10.1145/2692956.2663188

[18] William S. Moses, Lorenzo Chelini, Ruizhe Zhao, and Oleksandr Zinenko. 2021. Polygeist: Raising C to Polyhedral MLIR. In *Proc. 30th International Conference on Parallel Architectures and Compilation Techniques (PACT)*. 45–59. https://doi.org/10.1109/PACT52795.2021.00011

[19] Mohammad Mehdi Rahimifar, Quentin Winging, Berthié Gouin-Ferland, Hamza Ezzaooui Rahali, Charles-Étienne Granger, and Audrey C Therrien. 2023. Exploring Machine Learning to Hardware Implementations for Large Data Rate X-Ray Instrumentation. *Machine Learning: Science and Technology* 4, 4, 045035. https://doi.org/10.1088/2632-2153/ad0d12

[20] Alexa VanHattum, Daniel Schwartz-Narbonne, Nathan Chong, and Adrian Sampson. 2022. Verifying Dynamic Trait Objects in Rust. In *44th International Conference on Software Engineering: Software Engineering in Practice*. ACM, Pittsburgh Pennsylvania, 321–330. https://doi.org/10.1145/3510457.3513031

[21] Hanchen Ye, Cong Hao, Jianyi Cheng, Hyunmin Jeong, Jack Huang, Stephen Neuendorffer, and Deming Chen. 2022. ScaleHLS: A New Scalable High-Level Synthesis Framework on Multi-Level Intermediate Representation. In *2022 IEEE International Symposium on High-Performance Computer Architecture (HPCA)*. Seoul, Korea, Republic of, 741–755. https://doi.org/10.1109/HPCA53966.2022.00060

[22] Hanchen Ye, Hyegang Jun, and Deming Chen. 2024. HIDA: A Hierarchical Dataflow Compiler for High-Level Synthesis. In *29th ACM International Conference on Architectural Support for Programming Languages and Operating Systems, Volume 1 (La Jolla, CA, USA) (ASPLOS '24)*. Association for Computing Machinery, New York, NY, USA, 215–230. https://doi.org/10.1145/3617232.3624850