stdlib: add SimPoint checkpoint generator

The previous SimPoint warmup length was limited by the gaps between
the starting instruction of one SimPoint and the ending instruction of
the SimPoint before it. This was to prevent duplicate SimPoints, but it
can significantly limit the warmup length.
In this commit, the warmup length limitation will be extended to the
starting instruction of one SimPoint regardless of the gap between
SimPoints.
A SimPoint checkpoint generator is created to help taking checkpoints
for SimPoints and make sure multiple SimPoint checkpoints are taken
when there are multiple SimPoints sharing the same starting instruction

Change-Id: If95f6813e8cbf5c01e41135c1b1bb91ed2e950ad
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/64351
Maintainer: Bobby Bruce <bbruce@ucdavis.edu>
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jason Lowe-Power <power.jg@gmail.com>
Reviewed-by: Bobby Bruce <bbruce@ucdavis.edu>
diff --git a/src/python/gem5/components/processors/abstract_core.py b/src/python/gem5/components/processors/abstract_core.py
index 70335c3..2f4cb79 100644
--- a/src/python/gem5/components/processors/abstract_core.py
+++ b/src/python/gem5/components/processors/abstract_core.py
@@ -126,7 +126,8 @@
         """Schedule simpoint exit events for the core.
 
         This is used to raise SIMPOINT_BEGIN exit events in the gem5 standard
-        library.
+        library. Duplicate instruction counts in the inst_starts list will not
+        be scheduled.
 
         :param inst_starts: a list of SimPoints starting instructions
         :param init: if it is True, the starting instructions will be scheduled
diff --git a/src/python/gem5/components/processors/base_cpu_core.py b/src/python/gem5/components/processors/base_cpu_core.py
index 7f77ad5..db9a1a2 100644
--- a/src/python/gem5/components/processors/base_cpu_core.py
+++ b/src/python/gem5/components/processors/base_cpu_core.py
@@ -155,9 +155,9 @@
     @overrides(AbstractCore)
     def set_simpoint(self, inst_starts: List[int], init: bool) -> None:
         if init:
-            self.core.simpoint_start_insts = inst_starts
+            self.core.simpoint_start_insts = sorted(set(inst_starts))
         else:
-            self.core.scheduleSimpointsInstStop(inst_starts)
+            self.core.scheduleSimpointsInstStop(sorted(set(inst_starts)))
 
     @overrides(AbstractCore)
     def set_inst_stop_any_thread(self, inst: int, init: bool) -> None:
diff --git a/src/python/gem5/simulate/exit_event_generators.py b/src/python/gem5/simulate/exit_event_generators.py
index fc99dba..d6732bb 100644
--- a/src/python/gem5/simulate/exit_event_generators.py
+++ b/src/python/gem5/simulate/exit_event_generators.py
@@ -28,6 +28,7 @@
 import m5.stats
 from ..components.processors.abstract_processor import AbstractProcessor
 from ..components.processors.switchable_processor import SwitchableProcessor
+from ..utils.simpoint import SimPoint
 from m5.util import warn
 from pathlib import Path
 
@@ -130,3 +131,39 @@
     """
     while True:
         yield False
+
+
+def simpoints_save_checkpoint_generator(
+    checkpoint_dir: Path, simpoint: SimPoint
+):
+    """
+    A generator for taking multiple checkpoints for SimPoints. It will save the
+    checkpoints in the checkpoint_dir path with the SimPoints' index.
+    The Simulation run loop will continue after executing the behavior of the
+    generator until all the SimPoints in the simpoint_list has taken a
+    checkpoint.
+    """
+    simpoint_list = simpoint.get_simpoint_start_insts()
+    count = 0
+    last_start = -1
+    while True:
+        m5.checkpoint((checkpoint_dir / f"cpt.SimPoint{count}").as_posix())
+        last_start = simpoint_list[count]
+        count += 1
+        # When the next SimPoint starting instruction is the same as the last
+        # one, it will take a checkpoint for it with index+1. Because of there
+        # are cases that the warmup length is larger than multiple SimPoints
+        # starting instructions, then they might cause duplicates in the
+        # simpoint_start_ints.
+        while (
+            count < len(simpoint_list) and last_start == simpoint_list[count]
+        ):
+            m5.checkpoint((checkpoint_dir / f"cpt.SimPoint{count}").as_posix())
+            last_start = simpoint_list[count]
+            count += 1
+        # When there are remaining SimPoints in the list, let the Simulation
+        # loop continues, otherwise, exit the Simulation loop.
+        if count < len(simpoint_list):
+            yield False
+        else:
+            yield True
diff --git a/src/python/gem5/utils/simpoint.py b/src/python/gem5/utils/simpoint.py
index 9e50b2a..11d1087 100644
--- a/src/python/gem5/utils/simpoint.py
+++ b/src/python/gem5/utils/simpoint.py
@@ -139,16 +139,14 @@
         instruction length is the gap between the starting instruction of a
         SimPoint and the ending instruction of the last SimPoint.
         """
-        last = 0
         warmup_list = []
         for index, start_inst in enumerate(self._simpoint_start_insts):
-            warmup_inst = start_inst - warmup_interval - last
+            warmup_inst = start_inst - warmup_interval
             if warmup_inst < 0:
-                warmup_inst = start_inst - last
+                warmup_inst = start_inst
             else:
                 warmup_inst = warmup_interval
             warmup_list.append(warmup_inst)
-            last = start_inst + self._simpoint_interval
             # change the starting instruction of a SimPoint to include the
             # warmup instruction length
             self._simpoint_start_insts[index] = start_inst - warmup_inst