diff --git a/lbmpy/boundaries/boundaryconditions.py b/lbmpy/boundaries/boundaryconditions.py
index ac455a5f92f7798e6d9915e1ec25c6cb7a745505..e0c367f968492c74357bc0893d673f804fc8b2f8 100644
--- a/lbmpy/boundaries/boundaryconditions.py
+++ b/lbmpy/boundaries/boundaryconditions.py
@@ -3,7 +3,6 @@ import sympy as sp
 from lbmpy.boundaries.boundaryhandling import BoundaryOffsetInfo, LbmWeightInfo
 from lbmpy.simplificationfactory import create_simplification_strategy
 from pystencils import Assignment, Field
-from pystencils.astnodes import SympyAssignment
 from pystencils.data_types import create_type
 from pystencils.sympyextensions import get_symmetric_part
 
@@ -203,8 +202,8 @@ class FixedDensity(Boundary):
         subexpressions = [Assignment(eq.lhs, transformed_density if eq.lhs == density_symbol else eq.rhs)
                           for eq in symmetric_eq.subexpressions]
 
-        return subexpressions + [SympyAssignment(pdf_field[neighbor](inverse_dir),
-                                                 2 * eq_component - pdf_field(direction_symbol))]
+        return subexpressions + [Assignment(pdf_field[neighbor](inverse_dir),
+                                            2 * eq_component - pdf_field(direction_symbol))]
 
 
 class NeumannByCopy(Boundary):
diff --git a/lbmpy/boundaries_in_kernel.py b/lbmpy/boundaries_in_kernel.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1d44c72bde2e3081a6ee8d93bc5afc5eb7fe0ca
--- /dev/null
+++ b/lbmpy/boundaries_in_kernel.py
@@ -0,0 +1,146 @@
+import sympy as sp
+
+from lbmpy.boundaries.boundaryhandling import BoundaryOffsetInfo, LbmWeightInfo
+from pystencils.assignment import Assignment
+from pystencils.astnodes import LoopOverCoordinate
+from pystencils.data_types import cast_func
+from pystencils.field import Field
+from pystencils.simp.assignment_collection import AssignmentCollection
+from pystencils.stencil import inverse_direction
+from pystencils.sympyextensions import fast_subs
+
+
+def direction_indices_in_direction(direction, stencil):
+    for i, offset in enumerate(stencil):
+        for d_i, o_i in zip(direction, offset):
+            if (d_i == 1 and o_i == 1) or (d_i == -1 and o_i == -1):
+                yield i
+                break
+
+
+def boundary_substitutions(lb_method):
+    stencil = lb_method.stencil
+    w = lb_method.weights
+    replacements = {}
+    for idx, offset in enumerate(stencil):
+        symbolic_offset = BoundaryOffsetInfo.offset_from_dir(idx, dim=lb_method.dim)
+        for sym, value in zip(symbolic_offset, offset):
+            replacements[sym] = value
+
+        replacements[BoundaryOffsetInfo.inv_dir(idx)] = stencil.index(inverse_direction(offset))
+        replacements[LbmWeightInfo.weight_of_direction(idx)] = w[idx]
+    return replacements
+
+
+def transformed_boundary_rule(boundary, accessor, field, direction_symbol, lb_method, **kwargs):
+    tmp_field = field.new_field_with_different_name("_tmp")
+    rule = boundary(tmp_field, direction_symbol, lb_method, **kwargs)
+    bsubs = boundary_substitutions(lb_method)
+    rule = [a.subs(bsubs) for a in rule]
+    accessor_writes = accessor.write(tmp_field, lb_method.stencil)
+    to_replace = set()
+    for assignment in rule:
+        to_replace.update({fa for fa in assignment.atoms(Field.Access) if fa.field == tmp_field})
+
+    def compute_replacement(fa):
+        f = fa.index[0]
+        shift = accessor_writes[f].offsets
+        new_index = tuple(a + b for a, b in zip(fa.offsets, shift))
+        return field[new_index](accessor_writes[f].index[0])
+
+    substitutions = {fa: compute_replacement(fa) for fa in to_replace}
+    all_assignments = [assignment.subs(substitutions) for assignment in rule]
+    main_assignments = [a for a in all_assignments if isinstance(a.lhs, Field.Access)]
+    sub_expressions = [a for a in all_assignments if not isinstance(a.lhs, Field.Access)]
+    return AssignmentCollection(main_assignments, sub_expressions)
+
+
+def type_all_numbers(expr, dtype):
+    substitutions = {a: cast_func(a, dtype) for a in expr.atoms(sp.Number)}
+    return expr.subs(substitutions)
+
+
+def border_conditions(direction, field, ghost_layers=1):
+    abs_direction = tuple(-e if e < 0 else e for e in direction)
+    assert sum(abs_direction) == 1
+    idx = abs_direction.index(1)
+    val = direction[idx]
+
+    loop_ctrs = [LoopOverCoordinate.get_loop_counter_symbol(i) for i in range(len(direction))]
+    loop_ctr = loop_ctrs[idx]
+
+    gl = ghost_layers
+    border_condition = sp.Eq(loop_ctr, gl if val < 0 else field.shape[idx] - gl - 1)
+
+    if ghost_layers == 0:
+        return type_all_numbers(border_condition, loop_ctr.dtype)
+    else:
+        other_min = [sp.Ge(c, gl)
+                     for c in loop_ctrs if c != loop_ctr]
+        other_max = [sp.Lt(c, field.shape[i] - gl)
+                     for i, c in enumerate(loop_ctrs) if c != loop_ctr]
+        result = sp.And(border_condition, *other_min, *other_max)
+        return type_all_numbers(result, loop_ctr.dtype)
+
+
+def read_assignments_with_boundaries(collision_rule, pdf_field,
+                                     boundary_spec,
+                                     prev_timestep_accessor,
+                                     current_timestep_accessor):
+    method = collision_rule.method
+    result = {a: [b, a] for a, b in zip(current_timestep_accessor.read(pdf_field, method.stencil),
+                                        method.pre_collision_pdf_symbols)}
+
+    for direction, boundary in boundary_spec.items():
+        dir_indices = direction_indices_in_direction(direction, method.stencil)
+        border_cond = border_conditions(direction, pdf_field, ghost_layers=1)
+        for dir_index in dir_indices:
+            ac = transformed_boundary_rule(boundary, prev_timestep_accessor, pdf_field, dir_index,
+                                           method, index_field=None)
+            assignments = ac.new_without_subexpressions().main_assignments
+            assert len(assignments) == 1
+            assignment = assignments[0]
+            assert assignment.lhs in result
+
+            value_without_boundary = result[assignment.lhs][1]
+            result[assignment.lhs][1] = sp.Piecewise((assignment.rhs, border_cond),
+                                                     (value_without_boundary, True))
+
+    return AssignmentCollection([Assignment(*e) for e in result.values()])
+
+
+def update_rule_with_boundaries(collision_rule, input_field, output_field,
+                                boundaries,
+                                accessor, prev_accessor=None):
+    if prev_accessor is None:
+        prev_accessor = accessor
+
+    reads = read_assignments_with_boundaries(collision_rule, input_field, boundaries,
+                                             prev_timestep_accessor=prev_accessor,
+                                             current_timestep_accessor=accessor)
+
+    write_substitutions = {}
+    method = collision_rule.method
+    post_collision_symbols = method.post_collision_pdf_symbols
+    pre_collision_symbols = method.pre_collision_pdf_symbols
+
+    output_accesses = accessor.write(output_field, method.stencil)
+    input_accesses = accessor.read(input_field, method.stencil)
+
+    for (idx, offset), output_access in zip(enumerate(method.stencil), output_accesses):
+        write_substitutions[post_collision_symbols[idx]] = output_access
+
+    result = collision_rule.new_with_substitutions(write_substitutions)
+    result.subexpressions = reads.all_assignments + result.subexpressions
+
+    if 'split_groups' in result.simplification_hints:
+        all_substitutions = write_substitutions.copy()
+        for (idx, offset), input_access in zip(enumerate(method.stencil), input_accesses):
+            all_substitutions[pre_collision_symbols[idx]] = input_access
+
+        new_split_groups = []
+        for split_group in result.simplification_hints['split_groups']:
+            new_split_groups.append([fast_subs(e, all_substitutions) for e in split_group])
+        result.simplification_hints['split_groups'] = new_split_groups
+
+    return result