diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py
index 1928e81aab35b8842069d7f37dd4491b4ca20a4f..faf5d31daf1a3e038d353086d55a0634f1322520 100644
--- a/b_asic/core_operations.py
+++ b/b_asic/core_operations.py
@@ -523,6 +523,8 @@ class Division(AbstractOperation):
         return TypeName("div")
 
     def evaluate(self, a, b):
+        if b == 0:
+            return float("inf")
         return a / b
 
     @property
@@ -1372,6 +1374,8 @@ class Reciprocal(AbstractOperation):
         return TypeName("rec")
 
     def evaluate(self, a):
+        if a == 0:
+            return float("inf")
         return 1 / a
 
 
diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index 6dfe5809803b90be3c959f9c668d03d9794c9e54..7658fdb3aef71276ca1bc1e529aeb2f7e5839c75 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -365,10 +365,10 @@ class Schedule:
                     available_time = (
                         cast(int, source.latency_offset)
                         + self._start_times[source.operation.graph_id]
-                        - self._schedule_time * self._laps[signal.graph_id]
                     )
                     if available_time > self._schedule_time:
                         available_time -= self._schedule_time
+                    available_time -= self._schedule_time * self._laps[signal.graph_id]
                 else:
                     available_time = (
                         cast(int, source.latency_offset)
@@ -458,6 +458,26 @@ class Schedule:
             raise ValueError(
                 f"New schedule time ({time}) too short, minimum: {max_end_time}."
             )
+
+        # if updating the scheduling time -> update laps due to operations
+        # reading and writing in different iterations (across the edge)
+        if self._schedule_time is not None:
+            for signal_id in self._laps.keys():
+                port = self._sfg.find_by_id(signal_id).destination
+
+                source_port = port.signals[0].source
+                source_op = source_port.operation
+                source_port_start_time = self._start_times[source_op.graph_id]
+                source_port_latency_offset = source_op.latency_offsets[
+                    f"out{source_port.index}"
+                ]
+                if (
+                    source_port_start_time + source_port_latency_offset
+                    > self._schedule_time
+                    and source_port_start_time + source_port_latency_offset <= time
+                ):
+                    self._laps[signal_id] += 1
+
         self._schedule_time = time
         return self
 
@@ -855,6 +875,13 @@ class Schedule:
         ):
             new_start = self._schedule_time
             self._laps[op.input(0).signals[0].graph_id] -= 1
+        if (
+            new_start == 0
+            and isinstance(op, Input)
+            and self._laps[op.output(0).signals[0].graph_id] != 0
+        ):
+            new_start = 0
+            self._laps[op.output(0).signals[0].graph_id] -= 1
         # Set new start time
         self._start_times[graph_id] = new_start
         return self
@@ -936,7 +963,24 @@ class Schedule:
         destination_laps = []
         for signal_id, lap in self._laps.items():
             port = new_sfg.find_by_id(signal_id).destination
+
+            # if an operation lies across the scheduling time, place a delay after it
+            source_port = port.signals[0].source
+            source_op = source_port.operation
+            source_port_start_time = self._start_times[source_op.graph_id]
+            source_port_latency_offset = source_op.latency_offsets[
+                f"out{source_port.index}"
+            ]
+            if (
+                source_port_start_time + source_port_latency_offset
+                > self._schedule_time
+            ):
+                lap += (
+                    source_port_start_time + source_port_latency_offset
+                ) // self._schedule_time
+
             destination_laps.append((port.operation.graph_id, port.index, lap))
+
         for op, port, lap in destination_laps:
             for delays in range(lap):
                 new_sfg = new_sfg.insert_operation_before(op, Delay(), port)
diff --git a/b_asic/scheduler_gui/scheduler_item.py b/b_asic/scheduler_gui/scheduler_item.py
index fbb84fcd35d984ceab38b83f4e510cd24b3b6ee3..56a6e7fb1a61b8a77b0495f89c9f8390c38d1785 100644
--- a/b_asic/scheduler_gui/scheduler_item.py
+++ b/b_asic/scheduler_gui/scheduler_item.py
@@ -28,6 +28,7 @@ from b_asic.scheduler_gui.axes_item import AxesItem
 from b_asic.scheduler_gui.operation_item import OperationItem
 from b_asic.scheduler_gui.scheduler_event import SchedulerEvent
 from b_asic.scheduler_gui.signal_item import SignalItem
+from b_asic.special_operations import Output
 from b_asic.types import GraphID
 
 
@@ -121,7 +122,10 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup):
             raise ValueError("No schedule installed.")
         new_start_time = floor(pos) - floor(self._x_axis_indent)
         slacks = self.schedule.slacks(item.graph_id)
-        op_start_time = self.schedule.start_time_of_operation(item.graph_id)
+        op_start_time = (
+            self.schedule.start_time_of_operation(item.graph_id)
+            % self.schedule.schedule_time
+        )
         if not -slacks[0] <= new_start_time - op_start_time <= slacks[1]:
             # Cannot move due to dependencies
             return False
@@ -245,6 +249,13 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup):
         op_start_time = self.schedule.start_time_of_operation(item.graph_id)
         new_start_time = floor(pos) - floor(self._x_axis_indent)
         move_time = new_start_time - op_start_time
+        op = self._schedule.sfg.find_by_id(item.graph_id)
+        if (
+            isinstance(op, Output)
+            and op_start_time == self.schedule.schedule_time
+            and new_start_time < self.schedule.schedule_time
+        ):
+            move_time = new_start_time
         if move_time:
             self.schedule.move_operation(item.graph_id, move_time)
             print(f"schedule.move_operation({item.graph_id!r}, {move_time})")
diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py
index c0c714837b010da209b47d0f0dc3a6b54bfddeae..de1133ba7cb9d99ae1231d71bb0a03bd0b2bb779 100644
--- a/b_asic/signal_flow_graph.py
+++ b/b_asic/signal_flow_graph.py
@@ -1767,13 +1767,52 @@ class SFG(AbstractOperation):
         -------
         The iteration period bound.
         """
+        loops = self.loops
+        if not loops:
+            return -1
+
+        op_and_latency = {}
+        for op in self.operations:
+            for loop in loops:
+                for element in loop:
+                    if op.type_name() not in op_and_latency:
+                        op_and_latency[op.type_name()] = op.latency
+        t_l_values = []
+
+        for loop in loops:
+            loop.pop()
+            time_of_loop = 0
+            number_of_t_in_loop = 0
+            for element in loop:
+                if ''.join([i for i in element if not i.isdigit()]) == 't':
+                    number_of_t_in_loop += 1
+                for key, item in op_and_latency.items():
+                    if key in element:
+                        time_of_loop += item
+            if number_of_t_in_loop in (0, 1):
+                t_l_values.append(Fraction(time_of_loop, 1))
+            else:
+                t_l_values.append(Fraction(time_of_loop, number_of_t_in_loop))
+        return max(t_l_values)
+
+    @property
+    def loops(self) -> list[list[GraphID]]:
+        """
+        Return the recursive loops found in the SFG.
+
+        If -1, the SFG does not have any loops.
+
+        Returns
+        -------
+        A list of the recursive loops.
+        """
         inputs_used = []
         for used_input in self._used_ids:
             if 'in' in str(used_input):
                 used_input = used_input.replace("in", "")
                 inputs_used.append(int(used_input))
         if inputs_used == []:
-            raise ValueError("No inputs to sfg")
+            return []
         for input in inputs_used:
             input_op = self._input_operations[input]
         queue: Deque[Operation] = deque([input_op])
@@ -1795,39 +1834,24 @@ class SFG(AbstractOperation):
                             visited.add(new_op)
                     else:
                         raise ValueError("Destination does not exist")
-        if not dict_of_sfg:
-            raise ValueError(
-                "the SFG does not have any loops and therefore no iteration period bound."
-            )
         cycles = [
             [node] + path
             for node in dict_of_sfg
             for path in self._dfs(dict_of_sfg, node, node)
         ]
-        if not cycles:
-            return -1
-        op_and_latency = {}
-        for op in self.operations:
-            for lista in cycles:
-                for element in lista:
-                    if op.type_name() not in op_and_latency:
-                        op_and_latency[op.type_name()] = op.latency
-        t_l_values = []
-        for loop in cycles:
-            loop.pop()
-            time_of_loop = 0
-            number_of_t_in_loop = 0
-            for element in loop:
-                if ''.join([i for i in element if not i.isdigit()]) == 't':
-                    number_of_t_in_loop += 1
-                for key, item in op_and_latency.items():
-                    if key in element:
-                        time_of_loop += item
-            if number_of_t_in_loop in (0, 1):
-                t_l_values.append(Fraction(time_of_loop, 1))
-            else:
-                t_l_values.append(Fraction(time_of_loop, number_of_t_in_loop))
-        return max(t_l_values)
+
+        loops = self._get_non_redundant_cycles(cycles)
+        return loops
+
+    def _get_non_redundant_cycles(self, loops):
+        unique_lists = []
+        seen_cycles = set()
+        for loop in loops:
+            operation_set = frozenset(loop)
+            if operation_set not in seen_cycles:
+                unique_lists.append(loop)
+                seen_cycles.add(operation_set)
+        return unique_lists
 
     def state_space_representation(self):
         """
diff --git a/examples/scheduling_pipelining_retiming.py b/examples/scheduling_pipelining_retiming.py
new file mode 100644
index 0000000000000000000000000000000000000000..38a9601f7cf009b01490fcdc65510481db16c113
--- /dev/null
+++ b/examples/scheduling_pipelining_retiming.py
@@ -0,0 +1,111 @@
+"""
+=========================================
+Scheduling and Pipelining/Retiming
+=========================================
+
+When scheduling cyclically (modulo) there is implicit pipelining/retiming taking place.
+B-ASIC can easily be used to showcase this.
+"""
+
+import matplotlib.pyplot as plt
+import numpy as np
+from scipy import signal
+
+from b_asic.core_operations import Addition, ConstantMultiplication
+from b_asic.schedule import Schedule
+from b_asic.scheduler import ALAPScheduler
+from b_asic.sfg_generators import direct_form_1_iir
+from b_asic.signal_generator import Impulse
+from b_asic.simulation import Simulation
+
+# %%
+# Design a simple direct form IIR low-pass filter.
+N = 3
+Wc = 0.2
+b, a = signal.butter(N, Wc, btype="lowpass", output="ba")
+
+# %%
+# Generate the corresponding signal-flow-graph (SFG).
+sfg = direct_form_1_iir(b, a)
+sfg
+
+# %%
+# Set latencies and execution times of the operations.
+sfg.set_latency_of_type(Addition.type_name(), 1)
+sfg.set_latency_of_type(ConstantMultiplication.type_name(), 3)
+sfg.set_execution_time_of_type(Addition.type_name(), 1)
+sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1)
+
+# %%
+# Print the critical path Tcp and the iteration period bound Tmin.
+T_cp = sfg.critical_path_time()
+print("Tcp:", T_cp)
+T_min = sfg.iteration_period_bound()
+print("Tmin:", T_min)
+
+# %%
+# Create an ALAP schedule
+schedule = Schedule(sfg, scheduler=ALAPScheduler(), cyclic=True)
+schedule.show()
+
+# %%
+# Move some operations "over the edge" in order to reach Tcp = Tmin.
+schedule.move_operation('out0', 2)
+schedule.move_operation('add2', 2)
+schedule.move_operation('add0', 2)
+schedule.move_operation('add3', 2)
+schedule.set_schedule_time(5)
+schedule.show()
+
+# %%
+# Print the new critical path Tcp that is now equal to Tmin.
+T_cp = schedule.sfg.critical_path_time()
+print("Tcp:", T_cp)
+T_min = schedule.sfg.iteration_period_bound()
+print("Tmin:", T_min)
+
+# %%
+# Show the reconstructed SFG that is now pipelined/retimed compared to the original.
+schedule.sfg
+
+# %%
+# Simulate the impulse response of the original and reconstructed SFGs.
+# Plot the frequency responses of the original filter, the original SFG and the reconstructed SFG to verify
+# that the schedule is valid.
+sim1 = Simulation(sfg, [Impulse()])
+sim1.run_for(1000)
+
+sim2 = Simulation(schedule.sfg, [Impulse()])
+sim2.run_for(1000)
+
+w, h = signal.freqz(b, a)
+
+# Plot 1: Original filter
+spectrum_0 = 20 * np.log10(np.abs(h))
+plt.figure()
+plt.plot(w / np.pi, spectrum_0)
+plt.title("Original filter")
+plt.xlabel("Normalized frequency (x pi rad/sample)")
+plt.ylabel("Magnitude (dB)")
+plt.grid(True)
+plt.show()
+
+# Plot 2: Simulated SFG
+spectrum_1 = 20 * np.log10(np.abs(signal.freqz(sim1.results['0'])[1]))
+plt.figure()
+plt.plot(w / np.pi, spectrum_1)
+plt.title("Simulated SFG")
+plt.xlabel("Normalized frequency (x pi rad/sample)")
+plt.ylabel("Magnitude (dB)")
+plt.grid(True)
+plt.show()
+
+# Plot 3: Recreated SFG
+spectrum_2 = 20 * np.log10(np.abs(signal.freqz(sim2.results['0'])[1]))
+plt.figure()
+plt.plot(w / np.pi, spectrum_2)
+plt.title("Pipelined/retimed SFG")
+plt.xlabel("Normalized frequency (x pi rad/sample)")
+plt.ylabel("Magnitude (dB)")
+plt.grid(True)
+plt.show()
diff --git a/test/unit/test_core_operations.py b/test/unit/test_core_operations.py
index e8241e63dca92919975130a138e61294aa5d21da..3e2230ccf078fb81d006d99e4ae6c1464952fff3 100644
--- a/test/unit/test_core_operations.py
+++ b/test/unit/test_core_operations.py
@@ -185,6 +185,11 @@ class TestDivision:
         test_operation = Division(Addition(Input(), Constant(3)), Constant(3))
         assert test_operation.is_linear
 
+    def test_zero_input(self):
+        test_operation = Division()
+        assert test_operation.evaluate_output(0, [0, 1]) == 0
+        assert test_operation.evaluate_output(0, [1, 0]) == float("inf")
+
 
 class TestSquareRoot:
     """Tests for SquareRoot class."""
@@ -577,6 +582,10 @@ class TestReciprocal:
         test_operation = Reciprocal()
         assert test_operation.evaluate_output(0, [1 + 1j]) == 0.5 - 0.5j
 
+    def test_zero_input(self):
+        test_operation = Reciprocal()
+        assert test_operation.evaluate_output(0, [0]) == float("inf")
+
 
 class TestDepends:
     def test_depends_addition(self):
diff --git a/test/unit/test_list_schedulers.py b/test/unit/test_list_schedulers.py
index 34daa4d8a1443cee74a26c83cb8afabb7588e0fd..d788abd21b1b6a2732593158f0e5207e0d4d6171 100644
--- a/test/unit/test_list_schedulers.py
+++ b/test/unit/test_list_schedulers.py
@@ -468,6 +468,7 @@ class TestMaxFanOutScheduler:
             "out0": 36,
         }
         assert schedule.schedule_time == 36
+        _validate_recreated_sfg_ldlt_matrix_inverse(schedule, 3)
 
 
 class TestHybridScheduler:
@@ -829,6 +830,7 @@ class TestHybridScheduler:
             "out0": 16,
         }
         assert schedule.schedule_time == 16
+        _validate_recreated_sfg_ldlt_matrix_inverse(schedule, 2)
 
     def test_ldlt_inverse_2x2_specified_IO_times_cyclic(self):
         sfg = ldlt_matrix_inverse(N=2)
@@ -876,6 +878,21 @@ class TestHybridScheduler:
         }
         assert schedule.schedule_time == 16
 
+        # validate regenerated sfg with random 2x2 real s.p.d. matrix
+        A = np.random.rand(2, 2)
+        A = np.dot(A, A.T)
+        A_inv = np.linalg.inv(A)
+        input_signals = []
+        for i in range(2):
+            for j in range(i, 2):
+                input_signals.append(Constant(A[i, j]))
+
+        sim = Simulation(schedule.sfg, input_signals)
+        sim.run_for(2)
+        assert np.allclose(sim.results["0"], [A_inv[0, 0], A_inv[0, 0]])
+        assert np.allclose(sim.results["1"], [0, A_inv[0, 1]])
+        assert np.allclose(sim.results["2"], [0, A_inv[1, 1]])
+
     def test_invalid_max_resources(self):
         sfg = ldlt_matrix_inverse(N=2)
 
@@ -1157,6 +1174,7 @@ class TestHybridScheduler:
         direct, mem_vars = schedule.get_memory_variables().split_on_length()
         assert mem_vars.read_ports_bound() == 3
         assert mem_vars.write_ports_bound() == 1
+        _validate_recreated_sfg_ldlt_matrix_inverse(schedule, 3)
 
     def test_32_point_fft_custom_io_times(self):
         POINTS = 32
@@ -1672,6 +1690,7 @@ class TestHybridScheduler:
         }
 
         assert all([val == 0 for val in schedule.laps.values()])
+        _validate_recreated_sfg_ldlt_matrix_inverse(schedule, 3)
 
     def test_latency_offsets_cyclic(self):
         sfg = ldlt_matrix_inverse(
@@ -1953,3 +1972,26 @@ def _validate_recreated_sfg_fft(schedule: Schedule, points: int) -> None:
         a = res[str(i)]
         b = exp_res[i]
         assert np.isclose(a, b)
+
+
+def _validate_recreated_sfg_ldlt_matrix_inverse(schedule: Schedule, N: int) -> None:
+    # random real s.p.d matrix
+    A = np.random.rand(N, N)
+    A = np.dot(A, A.T)
+
+    # iterate through the upper diagonal and construct the input to the SFG
+    input_signals = []
+    for i in range(N):
+        for j in range(i, N):
+            input_signals.append(Constant(A[i, j]))
+
+    A_inv = np.linalg.inv(A)
+    sim = Simulation(schedule.sfg, input_signals)
+    sim.run_for(1)
+
+    # iterate through the upper diagonal and check
+    count = 0
+    for i in range(N):
+        for j in range(i, N):
+            assert np.isclose(sim.results[str(count)], A_inv[i, j])
+            count += 1
diff --git a/test/unit/test_sfg.py b/test/unit/test_sfg.py
index 471a8bde1ad6ae9e5953f1e4f22b0be5fe701cbc..090a11ddf6eb1b13ba9324232e21e9fe5f4c63b5 100644
--- a/test/unit/test_sfg.py
+++ b/test/unit/test_sfg.py
@@ -1913,6 +1913,27 @@ class TestIterationPeriodBound:
         assert sfg_two_inputs_two_outputs.iteration_period_bound() == -1
 
 
+class TestLoops:
+    def test_accumulator(self, sfg_simple_accumulator):
+        loops = sfg_simple_accumulator.loops
+        assert loops == [["add0", "t0", "add0"]]
+
+    def test_simple_filter(self, sfg_simple_filter):
+        loops = sfg_simple_filter.loops
+        assert loops == [["add0", "t0", "cmul0", "add0"]]
+
+    def test_direct_form_iir_filter(self, sfg_direct_form_iir_lp_filter):
+        loops = sfg_direct_form_iir_lp_filter.loops
+        assert loops == [
+            ["add0", "t0", "cmul4", "add1", "add0"],
+            ["add0", "t0", "t1", "cmul3", "add1", "add0"],
+        ]
+
+    def test_empty_sfg(self):
+        loops = SFG([], []).loops
+        assert loops == []
+
+
 class TestStateSpace:
     def test_accumulator(self, sfg_simple_accumulator):
         ss = sfg_simple_accumulator.state_space_representation()