diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..aaf7dbfade7660515be7aecf34aaaf898757c679
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,17 @@
+.PHONY: all build clean lj_ns
+
+all: build lj_ns clean
+	@echo "Everything was done!"
+
+build:
+	@echo "Building pairs package..."
+	python3 setup.py build && python3 setup.py install --user
+
+lj_ns:
+	@echo "Generating and compiling CPP for Lennard-Jones example..."
+	python3 examples/lj_func.py
+	g++ -o lj_ns lj_ns.cpp
+
+clean:
+	@echo "Cleaning..."
+	rm -rf build lj_ns lj_ns.cpp dist pairs.egg-info kernels kernels.pdf
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/pairs/analysis/devices.py b/src/pairs/analysis/devices.py
new file mode 100644
index 0000000000000000000000000000000000000000..94d13ba18b69bba4b9fa20b50fcc0975d5179db2
--- /dev/null
+++ b/src/pairs/analysis/devices.py
@@ -0,0 +1,66 @@
+from pairs.ir.visitor import Visitor
+
+
+class FetchKernelReferences(Visitor):
+    def __init__(self, ast):
+        super().__init__(ast)
+        self.kernel_stack = []
+        self.kernel_decls = {}
+        self.kernel_used_bin_ops = {}
+        self.writing = False
+
+    def visit_ArrayAccess(self, ast_node):
+        # Visit array and save current writing state
+        self.visit(ast_node.array)
+        writing_state = self.writing
+
+        # Index elements are read-only
+        self.writing = False
+        self.visit([roc for roc in ast_node.children() if roc != ast_node.array])
+        self.writing = writing_state
+
+    def visit_Assign(self, ast_node):
+        self.writing = True
+        self.visit(ast_node.destinations())
+        self.writing = False
+        self.visit(ast_node.sources())
+
+    def visit_Kernel(self, ast_node):
+        kernel_id = ast_node.kernel_id
+        self.kernel_decls[kernel_id] = []
+        self.kernel_used_bin_ops[kernel_id] = []
+        self.kernel_stack.append(ast_node)
+        self.visit_children(ast_node)
+        self.kernel_stack.pop()
+        ast_node.add_bin_op([b for b in self.kernel_used_bin_ops[kernel_id] if b not in self.kernel_decls[kernel_id]])
+
+    def visit_PropertyAccess(self, ast_node):
+        # Visit property and save current writing state
+        self.visit(ast_node.prop)
+        writing_state = self.writing
+
+        # Index elements are read-only
+        self.writing = False
+        self.visit([roc for roc in ast_node.children() if roc != ast_node.prop])
+        self.writing = writing_state
+
+    def visit_Decl(self, ast_node):
+        if isinstance(ast_node.elem, BinOp):
+            for k in self.kernel_stack:
+                self.kernel_decls[k.kernel_id].append(ast_node)
+
+    def visit_BinOp(self, ast_node):
+        for k in self.kernel_stack:
+            self.kernel_used_bin_ops[k.kernel_id].append(ast_node)
+
+    def visit_Array(self, ast_node):
+        for k in self.kernel_stack:
+            k.add_array(ast_node, self.writing)
+
+    def visit_Property(self, ast_node):
+        for k in self.kernel_stack:
+            k.add_property(ast_node, self.writing)
+
+    def visit_Var(self, ast_node):
+        for k in self.kernel_stack:
+            k.add_variable(ast_node, self.writing)
diff --git a/src/pairs/ir/kernel.py b/src/pairs/ir/kernel.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f7770c73562112be3a941381a99b49b6b585a03
--- /dev/null
+++ b/src/pairs/ir/kernel.py
@@ -0,0 +1,97 @@
+from pairs.ir.arrays import Array
+from pairs.ir.ast_node import ASTNode
+from pairs.ir.properties import Property
+from pairs.ir.variables import Var
+
+
+class Kernel(ASTNode):
+    last_kernel = 0
+
+    def __init__(self, sim, name=None, block=None):
+        super().__init__(sim)
+        self._id = Kernel.last_kernel
+        self._name = name if name is not None else "kernel" + str(Kernel.last_kernel)
+        self._variables = {}
+        self._arrays = {}
+        self._properties = {}
+        self._bin_ops = []
+        self._block = block
+        sim.add_kernel(self)
+        Kernel.last_kernel += 1
+
+    @property
+    def kernel_id(self):
+        return self._id
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def block(self):
+        return self._block
+
+    def variables(self):
+        return self._variables
+
+    def read_only_variables(self):
+        return [v for v in self._variables if 'w' not in self._variables[v]]
+
+    def write_variables(self):
+        return [v for v in self._variables if 'w' in self._variables[v]]
+
+    def arrays(self):
+        return self._arrays
+
+    def properties(self):
+        return self._properties
+
+    def properties_to_synchronize(self):
+        return {p for p in self._properties if self._properties[p][0] == 'r'}
+
+    def write_properties(self):
+        return {p for p in self._properties if 'w' in self._properties[p]}
+
+    def add_array(self, array, write=False):
+        array_list = array if isinstance(array, list) else [array]
+        character = 'w' if write else 'r'
+        for a in array_list:
+            assert isinstance(a, Array), "Kernel.add_array(): given element is not of type Array!"
+            self._arrays[a] = character if a not in self._arrays else self._arrays[a] + character
+
+    def add_variable(self, variable, write=False):
+        variable_list = variable if isinstance(variable, list) else [variable]
+        character = 'w' if write else 'r'
+        for v in variable_list:
+            assert isinstance(v, Var), "Kernel.add_variable(): given element is not of type Var!"
+            self._variables[v] = character if v not in self._variables else self._variables[v] + character
+
+    def add_property(self, prop, write=False):
+        prop_list = prop if isinstance(prop, list) else [prop]
+        character = 'w' if write else 'r'
+        for p in prop_list:
+            assert isinstance(p, Property), "Kernel.add_property(): given element is not of type Property!"
+            self._properties[p] = character if p not in self._properties else self._properties[p] + character
+
+    def add_bin_op(self, bin_op):
+        bin_op_list = bin_op if isinstance(bin_op, list) else [bin_op]
+        for b in bin_op_list:
+            assert isinstance(b, BinOp), "Kernel.add_bin_op(): given element is not of type BinOp!"
+            self._bin_ops.append(b)
+
+    def children(self):
+        return [self._block]
+
+
+class KernelLaunch(ASTNode):
+    def __init__(self, sim, kernel, iterator, range_min, range_max):
+        assert isinstance(module, Kernel), "KernelLaunch(): given parameter is not of type Kernel!"
+        super().__init__(sim)
+        self._kernel = kernel
+        self._iterator = iterator
+        self._range_min = range_min
+        self._range_max = range_max
+
+    @property
+    def kernel(self):
+        return self._kernel
diff --git a/src/pairs/ir/module.py b/src/pairs/ir/module.py
index d8ffcaf47e9d4ebd2a3cf5388c569bb903c1ab9d..7353dddcc937f86db962bdb5b282321c3a1dd540 100644
--- a/src/pairs/ir/module.py
+++ b/src/pairs/ir/module.py
@@ -9,7 +9,7 @@ class Module(ASTNode):
 
     def __init__(self, sim, name=None, block=None, resizes_to_check={}, check_properties_resize=False, run_on_device=False):
         super().__init__(sim)
-        self._name = name if name is not None else "module_" + str(Module.last_module)
+        self._name = name if name is not None else "module" + str(Module.last_module)
         self._variables = {}
         self._arrays = {}
         self._properties = {}
diff --git a/src/pairs/transformations/devices.py b/src/pairs/transformations/devices.py
index 530508135c3047ded26c80cae53e1bd930c0f6f4..1a9a037495cdf898efc2ed8747de444abef1dbcd 100644
--- a/src/pairs/transformations/devices.py
+++ b/src/pairs/transformations/devices.py
@@ -62,3 +62,25 @@ class AddDeviceCopies(Mutator):
 
         ast_node.stmts = new_stmts
         return ast_node
+
+
+class AddDeviceKernels(Mutator):
+    def __init__(self, ast):
+        super().__init__(ast)
+
+    def mutate_Module(self, ast_node):
+        ast_node._block = self.mutate(ast_node._block)
+
+        if ast_node.run_on_device:
+            new_stmts = []
+            kernel_id = 0
+            for s in ast_node._block.stmts:
+                if s is not None:
+                    if isinstance(s, For) and (not isinstance(s.min, Lit) or not isinstance(s.max, Lit)):
+                        kernel = Kernel(ast_node.sim, f"{ast_node.name}_kernel{kernel_id}", s.block)
+                        new_stmts.append(KernelLaunch(ast_node.sim, kernel, s.iterator, s.min, s.max))
+                        kernel_id += 1
+                    else:
+                        new_stmts.append(s)
+
+        return ast_node