diff --git a/examples/lj_func.py b/examples/lj_func.py
index 97d72009dd5a2ebeeab57f2c4ee72c851c4b3457..162d88df4b4a658b4d2cd520de41751cb3f4d770 100644
--- a/examples/lj_func.py
+++ b/examples/lj_func.py
@@ -31,4 +31,5 @@ psim.periodic(2.8)
 psim.vtk_output("output/test")
 psim.compute(lj, cutoff_radius, {'sigma6': sigma6, 'epsilon': epsilon})
 psim.compute(euler, symbols={'dt': dt})
+psim.target(pairs.target_gpu())
 psim.generate()
diff --git a/src/pairs/__init__.py b/src/pairs/__init__.py
index 98e050f763d8a0d64ab244ce37b96ebea28d8d93..75615c3f6bad7c19758e874342791649fed055d1 100644
--- a/src/pairs/__init__.py
+++ b/src/pairs/__init__.py
@@ -1,6 +1,13 @@
 from pairs.code_gen.cgen import CGen
+from pairs.code_gen.target import Target
 from pairs.sim.simulation import Simulation
 
 
 def simulation(ref, dims=3, timesteps=100, debug=False):
     return Simulation(CGen(f"{ref}.cpp", debug), dims, timesteps)
+
+def target_cpu():
+    return Target(Target.Backend_CPP, Target.Feature_CPU)
+
+def target_gpu():
+    return Target(Target.Backend_CUDA, Target.Feature_GPU)
diff --git a/src/pairs/code_gen/target.py b/src/pairs/code_gen/target.py
new file mode 100644
index 0000000000000000000000000000000000000000..30acef52b2dd7399a74742e5b943063c94183716
--- /dev/null
+++ b/src/pairs/code_gen/target.py
@@ -0,0 +1,39 @@
+class Target:
+    # Architectures
+    Arch_x86 = 0
+    Arch_Nvidia = 1
+
+    # Backend
+    Backend_CPP = 0
+    Backend_CUDA = 1
+    Backend_LLVM = 2
+
+    # Features
+    Feature_CPU = 1
+    Feature_AVX = 2
+    Feature_AVX2 = 3
+    Feature_AVX512 = 4
+    Feature_GPU = 5
+
+    # Operating system
+    OS_Unknown = 0
+    OS_Linux = 1
+    OS_Windows = 2
+
+    def __init__(self, backend, features, arch=None, os=None):
+        self.backend = backend
+        self.features = features if isinstance(features, list) else [features]
+        self.arch = arch if arch is not None else Target.Arch_x86 if Target.Feature_CPU in self.features else Target.Arch_Nvidia
+        self.os = os if os is not None else Target.OS_Unknown
+
+    def add_feature(self, feature):
+        self.features.append(feature)
+
+    def has_feature(self, feature):
+        return feature in self.features
+
+    def is_cpu(self):
+        return self.has_feature(Target.Feature_CPU)
+
+    def is_gpu(self):
+        return self.has_feature(Target.Feature_GPU)
diff --git a/src/pairs/sim/simulation.py b/src/pairs/sim/simulation.py
index ba8e8f79f7be9f3052f7439f6566bd406aa196a5..5aa9c4ca42bfb9c8936a92bfce2d0c13bd963a67 100644
--- a/src/pairs/sim/simulation.py
+++ b/src/pairs/sim/simulation.py
@@ -63,6 +63,7 @@ class Simulation:
         self.expr_id = 0
         self.iter_id = 0
         self.vtk_file = None
+        self._target = None
         self.nparticles = self.nlocal + self.nghost
         self.properties.add_capacity(self.particle_capacity)
 
@@ -214,7 +215,12 @@ class Simulation:
     def vtk_output(self, filename):
         self.vtk_file = filename
 
+    def target(self, target):
+        self._target = target
+
     def generate(self):
+        assert self._target is not None, "Target not specified!"
+
         timestep = Timestep(self, self.ntimesteps, [
             (EnforcePBC(self, self.pbc), 20),
             (SetupPBC(self, self.pbc), UpdatePBC(self, self.pbc), 20),
@@ -242,7 +248,8 @@ class Simulation:
         ])
 
         program = Module(self, name='main', block=Block.merge_blocks(decls, body))
-        add_copies = AddDeviceCopies(program)
+        if self._target.is_gpu():
+            add_copies = AddDeviceCopies(program)
 
         # Transformations
         lower_everything(program)
@@ -254,7 +261,9 @@ class Simulation:
         set_used_bin_ops(program)
         modularize(program)
         merge_adjacent_blocks(program)
-        add_copies.mutate()
+
+        if self._target.is_gpu():
+            add_copies.mutate()
 
         # For this part on, all bin ops are generated without usage verification
         self.check_decl_usage = False