diff --git a/miniapp/miniapp.cpp b/miniapp/miniapp.cpp
index c74db58a6d234553d0ef182d5c0bb6608f759cc9..4a54100519fa9468ee13857c8648a29655060176 100644
--- a/miniapp/miniapp.cpp
+++ b/miniapp/miniapp.cpp
@@ -105,11 +105,6 @@ int main(int argc, char** argv) {
 
         m.set_binning_policy(binning_policy, options.bin_dt);
 
-        // Inject some artificial spikes, 1 per 20 neurons.
-        for (cell_gid_type c=0; c<recipe->num_cells(); c+=20) {
-            m.add_artificial_spike({c, 0});
-        }
-
         // Attach samplers to all probes
         std::vector<std::unique_ptr<sample_trace_type>> traces;
         const time_type sample_dt = options.sample_dt;
@@ -195,6 +190,8 @@ std::unique_ptr<recipe> make_recipe(const io::cl_options& options, const probe_d
     p.morphology_round_robin = options.morph_rr;
 
     p.num_compartments = options.compartments_per_segment;
+
+    // TODO: Put all recipe parameters in the recipes file
     p.num_synapses = options.all_to_all? options.cells-1: options.synapses_per_cell;
     p.synapse_type = options.syn_type;
 
@@ -272,7 +269,7 @@ void report_compartment_stats(const recipe& rec) {
 
     for (std::size_t i = 0; i<ncell; ++i) {
         std::size_t ncomp = 0;
-        auto c = rec.get_cell(i);
+        auto c = rec.get_cell_description(i);
         if (auto ptr = util::any_cast<cell>(&c)) {
             ncomp = ptr->num_compartments();
         }
diff --git a/miniapp/miniapp_recipes.cpp b/miniapp/miniapp_recipes.cpp
index 32045e3aa635e3a673ee38638cf5a863724b8aad..e8c49422ef0e610e4478b7aaeefe9706d30d5fd6 100644
--- a/miniapp/miniapp_recipes.cpp
+++ b/miniapp/miniapp_recipes.cpp
@@ -4,9 +4,9 @@
 #include <utility>
 
 #include <cell.hpp>
+#include <rss_cell.hpp>
 #include <morphology.hpp>
 #include <util/debug.hpp>
-#include <util/unique_any.hpp>
 
 #include "miniapp_recipes.hpp"
 #include "morphology_pool.hpp"
@@ -79,9 +79,17 @@ public:
                             - param_.min_connection_delay_ms};
     }
 
-    cell_size_type num_cells() const override { return ncell_; }
+    cell_size_type num_cells() const override {
+        return ncell_ + 1;  // We automatically add a fake cell to each recipe!
+    }
+
+    util::unique_any get_cell_description(cell_gid_type i) const override {
+        // The last 'cell' is a rss_cell with one spike at t=0
+        if (i == ncell_) {
+            return util::unique_any(std::move(
+                rss_cell::rss_cell_description(0.0, 0.1, 0.1) ));
+        }
 
-    util::unique_any get_cell(cell_gid_type i) const override {
         auto gen = std::mt19937(i); // TODO: replace this with hashing generator...
 
         auto cc = get_cell_count_info(i);
@@ -113,8 +121,11 @@ public:
         return util::unique_any(std::move(cell));
     }
 
-    cell_kind get_cell_kind(cell_gid_type) const override {
-        // The basic_cell_recipe only produces mc cells, so return cable1d_neuron for now
+    cell_kind get_cell_kind(cell_gid_type i ) const override {
+        // The last 'cell' is a rss_cell with one spike at t=0
+        if (i == ncell_) {
+            return cell_kind::regular_spike_source;
+        }
         return cell_kind::cable1d_neuron;
     }
 
@@ -175,6 +186,12 @@ public:
 
     std::vector<cell_connection> connections_on(cell_gid_type i) const override {
         std::vector<cell_connection> conns;
+
+        // The rss_cell does not have inputs
+        if (i == ncell_) {
+            return conns;
+        }
+
         auto gen = std::mt19937(i); // TODO: replace this with hashing generator...
 
         cell_gid_type prev = i==0? ncell_-1: i-1;
@@ -183,6 +200,13 @@ public:
             cc.source = {prev, 0};
             cc.dest = {i, t};
             conns.push_back(cc);
+
+            // The rss_cell spikes at t=0, with these connections it looks like
+            // (source % 20) == 0 spikes at that moment.
+            if (prev % 20 == 0) {
+                cc.source = {ncell_, 0}; // also add connection from reg spiker!
+                conns.push_back(cc);
+            }
         }
 
         return conns;
@@ -207,6 +231,11 @@ public:
 
     std::vector<cell_connection> connections_on(cell_gid_type i) const override {
         std::vector<cell_connection> conns;
+
+        // The rss_cell does not have inputs
+        if (i == ncell_) {
+            return conns;
+        }
         auto conn_param_gen = std::mt19937(i); // TODO: replace this with hashing generator...
         auto source_gen = std::mt19937(i*123+457); // ditto
 
@@ -220,6 +249,13 @@ public:
             cc.source = {source, 0};
             cc.dest = {i, t};
             conns.push_back(cc);
+
+            // The rss_cell spikes at t=0, with these connections it looks like
+            // (source % 20) == 0 spikes at that moment.
+            if (source % 20 == 0) {
+                cc.source = {ncell_, 0};
+                conns.push_back(cc);
+            }
         }
 
         return conns;
@@ -249,6 +285,10 @@ public:
 
     std::vector<cell_connection> connections_on(cell_gid_type i) const override {
         std::vector<cell_connection> conns;
+        // The rss_cell does not have inputs
+        if (i == ncell_) {
+            return conns;
+        }
         auto conn_param_gen = std::mt19937(i); // TODO: replace this with hashing generator...
 
         for (unsigned t=0; t<param_.num_synapses; ++t) {
@@ -259,6 +299,13 @@ public:
             cc.source = {source, 0};
             cc.dest = {i, t};
             conns.push_back(cc);
+
+            // The rss_cell spikes at t=0, with these connections it looks like
+            // (source % 20) == 0 spikes at that moment.
+            if (source % 20 == 0) {
+                cc.source = {ncell_, 0};
+                conns.push_back(cc);
+            }
         }
 
         return conns;
diff --git a/src/cell_group_factory.cpp b/src/cell_group_factory.cpp
index 43abfaef8070af1f1dfbccda2fdda68bcafd7522..f47419d0b457e2a00c190e567c7aa716f6a619d5 100644
--- a/src/cell_group_factory.cpp
+++ b/src/cell_group_factory.cpp
@@ -2,6 +2,7 @@
 
 #include <backends.hpp>
 #include <cell_group.hpp>
+#include <rss_cell_group.hpp>
 #include <fvm_multicell.hpp>
 #include <mc_cell_group.hpp>
 #include <util/unique_any.hpp>
@@ -15,21 +16,21 @@ using mc_fvm_cell = mc_cell_group<fvm::fvm_multicell<multicore::backend>>;
 cell_group_ptr cell_group_factory(
         cell_kind kind,
         cell_gid_type first_gid,
-        const std::vector<util::unique_any>& cells,
+        const std::vector<util::unique_any>& cell_descriptions,
         backend_policy backend)
 {
-    if (backend==backend_policy::prefer_gpu) {
-        switch (kind) {
-        case cell_kind::cable1d_neuron:
-            return make_cell_group<gpu_fvm_cell>(first_gid, cells);
-        default:
-            throw std::runtime_error("unknown cell kind");
-        }
-    }
-
     switch (kind) {
     case cell_kind::cable1d_neuron:
-        return make_cell_group<mc_fvm_cell>(first_gid, cells);
+        if (backend == backend_policy::prefer_gpu) {
+            return make_cell_group<gpu_fvm_cell>(first_gid, cell_descriptions);
+        }
+        else {
+            return make_cell_group<mc_fvm_cell>(first_gid, cell_descriptions);
+        }
+
+    case cell_kind::regular_spike_source:
+        return make_cell_group<rss_cell_group>(first_gid, cell_descriptions);
+
     default:
         throw std::runtime_error("unknown cell kind");
     }
diff --git a/src/common_types.hpp b/src/common_types.hpp
index 81c7dbe8c2a8f28fddf6027ac7e20be79a862b07..d5c1a04bd983348a77354a47c7df67e639193f31 100644
--- a/src/common_types.hpp
+++ b/src/common_types.hpp
@@ -57,7 +57,8 @@ using time_type = float;
 // group equal kinds in the same cell group.
 
 enum cell_kind {
-    cable1d_neuron           // Our own special mc neuron
+    cable1d_neuron,           // Our own special mc neuron
+    regular_spike_source,     // Regular spiking source
 };
 
 } // namespace mc
diff --git a/src/mc_cell_group.hpp b/src/mc_cell_group.hpp
index d1e2dae8a8e9f160d901daa780267bbb96306747..f6ef43da4435fc3cfd06f0828be0491683002c62 100644
--- a/src/mc_cell_group.hpp
+++ b/src/mc_cell_group.hpp
@@ -35,25 +35,25 @@ public:
     mc_cell_group() = default;
 
     template <typename Cells>
-    mc_cell_group(cell_gid_type first_gid, const Cells& cells):
+    mc_cell_group(cell_gid_type first_gid, const Cells& cell_descriptions):
         gid_base_{first_gid}
     {
         // Create lookup structure for probe and target ids.
-        build_handle_partitions(cells);
+        build_handle_partitions(cell_descriptions);
         std::size_t n_probes = probe_handle_divisions_.back();
         std::size_t n_targets = target_handle_divisions_.back();
         std::size_t n_detectors = algorithms::sum(util::transform_view(
-            cells, [](const cell& c) { return c.detectors().size(); }));
+            cell_descriptions, [](const cell& c) { return c.detectors().size(); }));
 
         // Allocate space to store handles.
         target_handles_.resize(n_targets);
         probe_handles_.resize(n_probes);
 
-        lowered_.initialize(cells, target_handles_, probe_handles_);
+        lowered_.initialize(cell_descriptions, target_handles_, probe_handles_);
 
         // Create a list of the global identifiers for the spike sources
         auto source_gid = cell_gid_type{gid_base_};
-        for (const auto& cell: cells) {
+        for (const auto& cell: cell_descriptions) {
             for (cell_lid_type lid=0u; lid<cell.detectors().size(); ++lid) {
                 spike_sources_.push_back(source_id_type{source_gid, lid});
             }
@@ -63,9 +63,9 @@ public:
 
         // Create the enumeration of probes attached to cells in this cell group
         probes_.reserve(n_probes);
-        for (auto i: util::make_span(0, cells.size())){
+        for (auto i: util::make_span(0, cell_descriptions.size())){
             const cell_gid_type probe_gid = gid_base_ + i;
-            const auto probes_on_cell = cells[i].probes();
+            const auto probes_on_cell = cell_descriptions[i].probes();
             for (cell_lid_type lid: util::make_span(0, probes_on_cell.size())) {
                 // get the unique global identifier of this probe
                 cell_member_type id{probe_gid, lid};
@@ -79,11 +79,11 @@ public:
         }
     }
 
-    mc_cell_group(cell_gid_type first_gid, const std::vector<util::unique_any>& cells):
+    mc_cell_group(cell_gid_type first_gid, const std::vector<util::unique_any>& cell_descriptions):
         mc_cell_group(
             first_gid,
             util::transform_view(
-                cells,
+                cell_descriptions,
                 [](const util::unique_any& c) -> const cell& {return util::any_cast<const cell&>(c);})
         )
     {}
diff --git a/src/model.cpp b/src/model.cpp
index 9d0fb262bf4813725e32fe8394340533f589f106..586f43139eaefb93f38b43032bb1731710261eed 100644
--- a/src/model.cpp
+++ b/src/model.cpp
@@ -31,15 +31,15 @@ model::model(const recipe& rec, const domain_decomposition& decomp):
             PE("setup", "cells");
 
             auto group = domain_.get_group(i);
-            std::vector<util::unique_any> cells(group.end-group.begin);
+            std::vector<util::unique_any> cell_descriptions(group.end-group.begin);
 
             for (auto gid: util::make_span(group.begin, group.end)) {
                 auto i = gid-group.begin;
-                cells[i] = rec.get_cell(gid);
+                cell_descriptions[i] = rec.get_cell_description(gid);
             }
 
             cell_groups_[i] = cell_group_factory(
-                    group.kind, group.begin, cells, domain_.backend());
+                    group.kind, group.begin, cell_descriptions, domain_.backend());
             PL(2);
         });
 
diff --git a/src/recipe.hpp b/src/recipe.hpp
index 496543be3d72dca7ca5d872fd3989e09e51dd24e..8dd7565ad7f84b5dc5b99462b72d523227af6354 100644
--- a/src/recipe.hpp
+++ b/src/recipe.hpp
@@ -48,7 +48,7 @@ class recipe {
 public:
     virtual cell_size_type num_cells() const =0;
 
-    virtual util::unique_any get_cell(cell_gid_type) const =0;
+    virtual util::unique_any get_cell_description(cell_gid_type) const =0;
     virtual cell_kind get_cell_kind(cell_gid_type) const = 0;
 
     virtual cell_count_info get_cell_count_info(cell_gid_type) const =0;
@@ -70,7 +70,7 @@ public:
         return 1;
     }
 
-    util::unique_any get_cell(cell_gid_type) const override {
+    util::unique_any get_cell_description(cell_gid_type) const override {
         return util::unique_any(cell(clone_cell, cell_));
     }
 
diff --git a/src/rss_cell.hpp b/src/rss_cell.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7f627ffc965d61733692ec665fa7bbb0146b580a
--- /dev/null
+++ b/src/rss_cell.hpp
@@ -0,0 +1,95 @@
+#pragma once
+#pragma once
+
+#include <vector>
+
+#include <common_types.hpp>
+#include <util/debug.hpp>
+
+namespace nest {
+namespace mc {
+
+/// Regular spike source: A cell that generated spikes with a certain
+/// period for a set time range.
+class rss_cell {
+public:
+    using index_type = cell_lid_type;
+    using size_type = cell_local_size_type;
+    using value_type = double;
+
+    struct rss_cell_description {
+        time_type start_time;
+        time_type period;
+        time_type stop_time;
+
+        rss_cell_description(time_type st, time_type per, time_type stop):
+            start_time(st),
+            period(per),
+            stop_time(stop)
+        {}
+    };
+
+    /// Construct a rss cell from its description
+    rss_cell(rss_cell_description descr) :
+        start_time_(descr.start_time),
+        period_(descr.period),
+        stop_time_(descr.stop_time),
+        time_(0.0)
+    {}
+
+    /// Construct a rss cell from individual arguments
+    rss_cell(time_type start_time=0.0, time_type period=1.0, time_type stop_time=0.0):
+        start_time_(start_time),
+        period_(period),
+        stop_time_(stop_time),
+        time_(0.0)
+    {}
+
+    /// Return the kind of cell, used for grouping into cell_groups
+    cell_kind  get_cell_kind() const  {
+        return cell_kind::regular_spike_source;
+    }
+
+    /// Collect all spikes until tfinal.
+    // updates the internal time state to tfinal as a side effect
+    std::vector<time_type> spikes_until(time_type tfinal) {
+        std::vector<time_type> spike_times;
+
+        // If we should be spiking in this 'period'
+        if (tfinal > start_time_) {
+            // We have to spike till tfinal or the stop_time_of the neuron
+            auto end_time = stop_time_ < tfinal ? stop_time_ : tfinal;
+            // Generate all possible spikes in this time frame typically only
+            for (time_type time = start_time_ > time_ ? start_time_ : time_;
+                time < end_time;
+                time += period_) {
+                spike_times.push_back(time);
+            }
+        }
+        // Save our current time we generate exclusive a possible spike at tfinal
+        time_ = tfinal;
+
+        return spike_times;
+    }
+
+    /// reset internal time to 0.0
+    void reset() {
+        time_ = 0.0;
+    }
+
+private:
+    // When to start spiking
+    time_type start_time_;
+
+    // with what period
+    time_type period_;
+
+    // untill when
+    time_type stop_time_;
+
+    // internal time, storage
+    time_type time_;
+};
+
+} // namespace mc
+} // namespace nest
diff --git a/src/rss_cell_group.hpp b/src/rss_cell_group.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8ec5b9cb10e7cbe8935996f579f7d92d64fdbc19
--- /dev/null
+++ b/src/rss_cell_group.hpp
@@ -0,0 +1,97 @@
+#pragma once
+
+#include <cell_group.hpp>
+#include <rss_cell.hpp>
+#include <util/span.hpp>
+#include <util/unique_any.hpp>
+
+
+#include <iostream>
+namespace nest {
+namespace mc {
+
+/// Cell_group to collect cells that spike at a set frequency
+/// Cell are lightweight and are not executed in anybackend implementation
+class rss_cell_group : public cell_group {
+public:
+    using source_id_type = cell_member_type;
+
+    rss_cell_group(cell_gid_type first_gid, const std::vector<util::unique_any>& cell_descriptions):
+        gid_base_(first_gid)
+    {
+        using util::make_span;
+
+        for (cell_gid_type i: make_span(0, cell_descriptions.size())) {
+            // Copy all the rss_cells
+            cells_.push_back(rss_cell(
+                util::any_cast<rss_cell::rss_cell_description>(cell_descriptions[i])
+            ));
+
+            // create a lid to gid map
+            spike_sources_.push_back({gid_base_+i, 0});
+        }
+    }
+
+    virtual ~rss_cell_group() = default;
+
+    cell_kind get_cell_kind() const override {
+        return cell_kind::regular_spike_source;
+    }
+
+    void reset() override {
+        for (auto cell: cells_) {
+            cell.reset();
+        }
+    }
+
+    void set_binning_policy(binning_kind policy, time_type bin_interval) override
+    {} // Nothing to do?
+
+    void advance(time_type tfinal, time_type dt) override {
+        // TODO: Move source information to rss_cell implementation
+        for (auto i: util::make_span(0, cells_.size())) {
+            for (auto spike_time: cells_[i].spikes_until(tfinal)) {
+                spikes_.push_back({spike_sources_[i], spike_time});
+            }
+        }
+    };
+
+    void enqueue_events(const std::vector<postsynaptic_spike_event>& events) override {
+        std::logic_error("The rss_cells do not support incoming events!");
+    }
+
+    const std::vector<spike>& spikes() const override {
+        return spikes_;
+    }
+
+    void clear_spikes() override {
+        spikes_.clear();
+    }
+
+    std::vector<probe_record> probes() const override {
+        return probes_;
+    }
+
+    void add_sampler(cell_member_type probe_id, sampler_function s, time_type start_time = 0) override {
+        std::logic_error("The rss_cells do not support sampling of internal state!");
+    }
+
+private:
+    // gid of first cell in group.
+    cell_gid_type gid_base_;
+
+    // Spikes that are generated.
+    std::vector<spike> spikes_;
+
+    // Spike generators attached to the cell
+    std::vector<source_id_type> spike_sources_;
+
+    // Store a reference to the cell actually implementing the spiking
+    std::vector<rss_cell> cells_;
+
+    std::vector<probe_record> probes_;
+};
+
+} // namespace mc
+} // namespace nest
+
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index d5a9353348015c2bfd098ac28db038e194114ec2..476d19614a99837ac376705620fb0c603e4561d7 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -16,6 +16,8 @@ add_subdirectory(performance)
 # Microbenchmarks.
 add_subdirectory(ubench)
 
+# regression / delta tests
+# Employing the full simulator. validated using deltas on output data
 
 # modcc tests
 if(NOT use_external_modcc)
@@ -25,9 +27,6 @@ endif()
 
 # Proposed additional test types:
 
-# Large test, employing the full simulator. validated using deltas on output data
-
 # Test to check integration between components
 
-
 # Numbered tests based on bugs in the tracker
diff --git a/tests/unit/test_domain_decomposition.cpp b/tests/unit/test_domain_decomposition.cpp
index a57f83a5b5afaa7263809339d344c026aaecce0f..39e9a03837e2a765322bf71bec78550c0e5f161a 100644
--- a/tests/unit/test_domain_decomposition.cpp
+++ b/tests/unit/test_domain_decomposition.cpp
@@ -17,7 +17,7 @@ public:
         return size_;
     }
 
-    util::unique_any get_cell(cell_gid_type) const override {
+    util::unique_any get_cell_description(cell_gid_type) const override {
         return {};
     }
     cell_kind get_cell_kind(cell_gid_type) const override {
diff --git a/tests/unit/test_rss_cell.cpp b/tests/unit/test_rss_cell.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e12bbb472743db453a4097bfae2bcf5293326996
--- /dev/null
+++ b/tests/unit/test_rss_cell.cpp
@@ -0,0 +1,57 @@
+#include "../gtest.h"
+
+#include "rss_cell.hpp"
+
+using namespace  nest::mc;
+
+TEST(rss_cell, constructor)
+{
+    rss_cell test(0.0, 0.01, 1.0);
+}
+
+TEST(rss_cell, basic_usage)
+{
+    rss_cell sut(0.1, 0.01, 0.2);
+
+    // no spikes in this time frame
+    auto spikes = sut.spikes_until(0.09);
+    EXPECT_EQ(size_t(0), spikes.size());
+
+    //only on in this time frame
+    spikes = sut.spikes_until(0.11);
+    EXPECT_EQ(size_t(1), spikes.size());
+
+    // Reset the internal state to null
+    sut.reset();
+
+    // Expect 10 excluding the 0.2
+    spikes = sut.spikes_until(0.2);
+    EXPECT_EQ(size_t(10), spikes.size());
+}
+
+TEST(rss_cell, poll_time_after_end_time)
+{
+    rss_cell sut(0.1, 0.01, 0.2);
+
+    // no spikes in this time frame
+    auto spikes = sut.spikes_until(0.3);
+    EXPECT_EQ(size_t(10), spikes.size());
+
+    // now ask for spikes for a time slot already passed.
+    spikes = sut.spikes_until(0.2);
+    // It should result in zero spikes because of the internal state!
+    EXPECT_EQ(size_t(0), spikes.size());
+
+    sut.reset();
+
+    // Expect 10 excluding the 0.2
+    spikes = sut.spikes_until(0.2);
+    EXPECT_EQ(size_t(10), spikes.size());
+}
+
+TEST(rss_cell, cell_kind_correct)
+{
+    rss_cell sut(0.1, 0.01, 0.2);
+
+    EXPECT_EQ(cell_kind::regular_spike_source, sut.get_cell_kind());
+}