From 41e92105efe1ae8da97a92b039943bcafbd331d7 Mon Sep 17 00:00:00 2001 From: Jonathan Tan MSI Date: Wed, 3 Dec 2025 23:14:58 -0600 Subject: [PATCH 1/7] Added m and k property for (m,k) requirements of tasks --- simso/configuration/Configuration.py | 5 +++-- simso/core/Job.py | 9 ++++++++- simso/core/Task.py | 8 ++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/simso/configuration/Configuration.py b/simso/configuration/Configuration.py index 4c0f2d7..15b3598 100644 --- a/simso/configuration/Configuration.py +++ b/simso/configuration/Configuration.py @@ -281,7 +281,8 @@ def add_task(self, name, identifier, task_type="Periodic", abort_on_miss=True, period=10, activation_date=0, n_instr=0, mix=0.5, stack_file="", wcet=0, acet=0, et_stddev=0, deadline=10, base_cpi=1.0, followed_by=None, - list_activation_dates=[], preemption_cost=0, data=None): + list_activation_dates=[], preemption_cost=0, data=None, + m=1, k=1): """ Helper method to create a TaskInfo and add it to the list of tasks. """ @@ -292,7 +293,7 @@ def add_task(self, name, identifier, task_type="Periodic", activation_date, n_instr, mix, (stack_file, self.cur_dir), wcet, acet, et_stddev, deadline, base_cpi, followed_by, list_activation_dates, - preemption_cost, data) + preemption_cost, data, m=m, k=k) self.task_info_list.append(task) return task diff --git a/simso/core/Job.py b/simso/core/Job.py index 4708fd3..4aa3be9 100644 --- a/simso/core/Job.py +++ b/simso/core/Job.py @@ -2,7 +2,7 @@ from SimPy.Simulation import Process, hold, passivate from simso.core.JobEvent import JobEvent -from math import ceil +from math import floor, ceil class Job(Process): @@ -29,6 +29,8 @@ def __init__(self, task, name, pred, monitor, etm, sim): """ Process.__init__(self, name=name, sim=sim) self._task = task + self._m = task._task_info.m + self._k = task._task_info.k self._pred = pred self.instr_count = 0 # Updated by the cache model. self._computation_time = 0 @@ -49,6 +51,11 @@ def __init__(self, task, name, pred, monitor, etm, sim): self.context_ok = True # The context is ready to be loaded. + self.instance_num = int(name.split("_")[1]) + self.cri = floor(ceil(self.instance_num * self._m/ self._k) * (self._k / self._m)) + self.mandatory = self.instance_num == self.cri + self.optional = not self.mandatory + def is_active(self): """ Return True if the job is still active. diff --git a/simso/core/Task.py b/simso/core/Task.py index da5ad2d..25b5dcf 100644 --- a/simso/core/Task.py +++ b/simso/core/Task.py @@ -20,7 +20,8 @@ class TaskInfo(object): def __init__(self, name, identifier, task_type, abort_on_miss, period, activation_date, n_instr, mix, stack_file, wcet, acet, et_stddev, deadline, base_cpi, followed_by, - list_activation_dates, preemption_cost, data): + list_activation_dates, preemption_cost, data, + m=1, k=1): """ :type name: str :type identifier: int @@ -63,6 +64,9 @@ def __init__(self, name, identifier, task_type, abort_on_miss, period, self.data = data self.preemption_cost = preemption_cost + self.m = m + self.k = k + @property def csdp(self): """ @@ -272,7 +276,7 @@ def create_job(self, pred=None): directly by a scheduler. """ self._job_count += 1 - job = Job(self, "{}_{}".format(self.name, self._job_count), pred, + job = Job(self, "{}_{}".format(self.name, self._job_count - 1), pred, #-1 to be zero indexed monitor=self._monitor, etm=self._etm, sim=self.sim) if len(self._activations_fifo) == 0: From 1a6d8e1883510b96d2ed04efaaf7dcdf533b378c Mon Sep 17 00:00:00 2001 From: Jonathan Tan MSI Date: Wed, 3 Dec 2025 23:23:45 -0600 Subject: [PATCH 2/7] Implemented (m,k)-firm RMS scheduler --- simso/schedulers/MK_RMS.py | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 simso/schedulers/MK_RMS.py diff --git a/simso/schedulers/MK_RMS.py b/simso/schedulers/MK_RMS.py new file mode 100644 index 0000000..000c7e0 --- /dev/null +++ b/simso/schedulers/MK_RMS.py @@ -0,0 +1,48 @@ +from simso.core import Scheduler +from simso.schedulers import scheduler + +from math import floor, ceil + +@scheduler("simso.schedulers.MK_RMS") +class MK_RMS(Scheduler): + """ Rate monotonic """ + def init(self): + self.ready_list = [] + + def on_activate(self, job): + self.ready_list.append(job) + job.cpu.resched() + + def on_terminated(self, job): + if job in self.ready_list: + self.ready_list.remove(job) + else: + job.cpu.resched() + + def schedule(self, cpu): + decision = None + if self.ready_list: + ############### As far as I can tell this chunk is just selecting a CPU, so for uniprocessor, this chunk is junk ############### + # Get a free processor or a processor running a low priority job. + key = lambda x: ( # this lambda func aparently returns a 3 element tuple + 0 if x.running is None else 1, + -x.running.period if x.running else 0, + 0 if x is cpu else 1 + ) + cpu_min = min(self.processors, key=key) # with one cpu, cpu min will just be the only cpu + ####################################################### end junk chunk ####################################################### + + # Job with highest priority. + job = min(self.ready_list, key=lambda x: (x.optional, x.period)) + + if (self.sim.now()/1000000 >= 25.0): + pass + + if (((cpu_min.running is None) or (cpu_min.running.period > job.period)) and not ((cpu_min.running is not None and cpu_min.running.mandatory) and job.optional)): + self.ready_list.remove(job) + if cpu_min.running: + self.ready_list.append(cpu_min.running) # if it got preempted, add it back to the que + decision = (job, cpu_min) + print(self.sim.now()/1000000, job.name, cpu_min.name) + + return decision From e6155e4b8d2d9c98cb374d1e999f745c73e49abf Mon Sep 17 00:00:00 2001 From: Jonathan Tan MSI Date: Thu, 4 Dec 2025 00:57:46 -0600 Subject: [PATCH 3/7] Code cleanup --- simso/schedulers/MK_RMS.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/simso/schedulers/MK_RMS.py b/simso/schedulers/MK_RMS.py index 000c7e0..9174d45 100644 --- a/simso/schedulers/MK_RMS.py +++ b/simso/schedulers/MK_RMS.py @@ -35,10 +35,9 @@ def schedule(self, cpu): # Job with highest priority. job = min(self.ready_list, key=lambda x: (x.optional, x.period)) - if (self.sim.now()/1000000 >= 25.0): - pass + if (((cpu_min.running is None) or (cpu_min.running.period > job.period)) and + not ((cpu_min.running is not None and cpu_min.running.mandatory) and job.optional)): # this check is to prevent a optional job preempting a mandatory job - if (((cpu_min.running is None) or (cpu_min.running.period > job.period)) and not ((cpu_min.running is not None and cpu_min.running.mandatory) and job.optional)): self.ready_list.remove(job) if cpu_min.running: self.ready_list.append(cpu_min.running) # if it got preempted, add it back to the que From 59b8c9a1c6c0721e5fe8f1b7ded26545880b5f3a Mon Sep 17 00:00:00 2001 From: Jonathan Tan MSI Date: Thu, 4 Dec 2025 00:58:09 -0600 Subject: [PATCH 4/7] Implemented (m,k)-firm EDF Scheduler --- simso/schedulers/MK_EDF.py | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 simso/schedulers/MK_EDF.py diff --git a/simso/schedulers/MK_EDF.py b/simso/schedulers/MK_EDF.py new file mode 100644 index 0000000..b3a647a --- /dev/null +++ b/simso/schedulers/MK_EDF.py @@ -0,0 +1,50 @@ +""" +Implementation of the Global-EDF (Earliest Deadline First) for multiprocessor +architectures. +""" +from simso.core import Scheduler +from simso.schedulers import scheduler + +@scheduler("simso.schedulers.MK_EDF") +class MK_EDF(Scheduler): + """Earliest Deadline First""" + def on_activate(self, job): + job.cpu.resched() + + def on_terminated(self, job): + job.cpu.resched() + + def schedule(self, cpu): + # List of ready jobs not currently running: + ready_jobs = [t.job for t in self.task_list + if t.is_active() and not t.job.is_running()] + + if ready_jobs: + ############### As far as I can tell this chunk is just selecting a CPU, so for uniprocessor, this chunk is junk ############### + # Select a free processor or, if none, + # the one with the greatest deadline (self in case of equality): + key = lambda x: ( + 1 if not x.running else 0, + x.running.absolute_deadline if x.running else 0, + 1 if x is cpu else 0 + ) + cpu_min = max(self.processors, key=key) + ####################################################### end junk chunk ####################################################### + + + # Select the job with the least priority: + job = min(ready_jobs, key=lambda x: (x.optional, x.absolute_deadline, x.period)) + + if ((# If there is nothing running rn, or + (cpu_min.running is None) or + # if the current running is optional and the job is mandatory, also if the current running deadline < job deadline, or + ((cpu_min.running is not None and job is not None) and (cpu_min.running.optional and job.mandatory) and (cpu_min.running.absolute_deadline < job.absolute_deadline)) or + # if the current running deadline is > the job's deadline + (cpu_min.running.absolute_deadline > job.absolute_deadline) + ) and + # finally we make sure a mandatory task don't get preempted by a optional task + not ((cpu_min.running is not None and cpu_min.running.mandatory) and job.optional) + ): + + print(self.sim.now()/1000000, job.name, cpu_min.name) + return (job, cpu_min) From b7d73a721d66ea8c386af04551e56d7e80dc8d12 Mon Sep 17 00:00:00 2001 From: Jonathan Tan MSI Date: Fri, 5 Dec 2025 00:09:19 -0600 Subject: [PATCH 5/7] Modified comments --- simso/schedulers/MK_EDF.py | 2 +- simso/schedulers/MK_RMS.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/simso/schedulers/MK_EDF.py b/simso/schedulers/MK_EDF.py index b3a647a..b6c2460 100644 --- a/simso/schedulers/MK_EDF.py +++ b/simso/schedulers/MK_EDF.py @@ -7,7 +7,7 @@ @scheduler("simso.schedulers.MK_EDF") class MK_EDF(Scheduler): - """Earliest Deadline First""" + """Earliest Deadline First with (m,k)-firm requirements""" def on_activate(self, job): job.cpu.resched() diff --git a/simso/schedulers/MK_RMS.py b/simso/schedulers/MK_RMS.py index 9174d45..ca1e95d 100644 --- a/simso/schedulers/MK_RMS.py +++ b/simso/schedulers/MK_RMS.py @@ -5,7 +5,7 @@ @scheduler("simso.schedulers.MK_RMS") class MK_RMS(Scheduler): - """ Rate monotonic """ + """ Rate monotonic with (m,k)-firm requirements """ def init(self): self.ready_list = [] From 0df80e93b75a87afdbad365c5df88b5ae8cccb52 Mon Sep 17 00:00:00 2001 From: Jonathan Tan MSI Date: Fri, 5 Dec 2025 00:38:01 -0600 Subject: [PATCH 6/7] Added code for (m,k)-firm DBP Scheduler --- simso/schedulers/MK_DBP.py | 153 +++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 simso/schedulers/MK_DBP.py diff --git a/simso/schedulers/MK_DBP.py b/simso/schedulers/MK_DBP.py new file mode 100644 index 0000000..ae79803 --- /dev/null +++ b/simso/schedulers/MK_DBP.py @@ -0,0 +1,153 @@ +from collections import deque + +from simso.core import Scheduler +from simso.schedulers import scheduler + + +@scheduler("simso.schedulers.MK_DBP") +class MK_DBP(Scheduler): + """ + Distance Based Priority scheduler for (m,k)-firm tasks. + + Based on: Joël Goossens. (m, k)-firm constraints and DBP scheduling: impact of the initial + k-sequence and exact schedulability test. 16th International Conference on Real-Time and + Network Systems (RTNS 2008), Isabelle Puaut, Oct 2008, Rennes, France. ⟨inria-00336461⟩ + + This is coded to work on Uniprocessor simulations! + """ + + def init(self): + self.ready_list = [] + self._k_histories = {} + for task in self.task_list: + self._k_histories[task] = self._init_history(task) + + def on_activate(self, job): + self.ready_list.append(job) + job.cpu.resched() + + def on_terminated(self, job): + self._update_history(job) + if job in self.ready_list: + self.ready_list.remove(job) + else: + job.cpu.resched() + + def _should_run(self, candidate, current): + if current is None: + return True + if current.mandatory and candidate.optional: + return False + return self._priority_tuple(candidate) < self._priority_tuple(current) + + def _priority_tuple(self, job): + return ( + job.optional, # mandatory > optional + self._compute_distance(job.task), # distance + job.absolute_deadline, # on tie use EDF + job.period # then RMS + ) + + def _priority_distance(self, job): + if job is None: + return float("inf") + return self._compute_distance(job.task) + + def _compute_distance(self, task): + """ + Distance, as per Goossens 2008, is defined as starting from time now, the amount of + deadline we can affort to miss before we violate the (m,k)-firm requirement. + + In practice, we achieve this but keeping track of the history of the task as a list. + if successes is < m the distance is 0 because it is already violated (m,k)-firm + requirement. Else, it append a hypothetical miss into the list, then it runs the check + again. + """ + history = self._k_histories.get(task) + if history is None: + history = self._init_history(task) + self._k_histories[task] = history + m = getattr(task._task_info, "m", 1) + if m <= 0: + return float("inf") + sequence = list(history) + successes = sum(sequence) + if successes < m: + return 0 + allowed = 0 + current = sequence[:] + k = len(current) + while allowed < k: + current = current[1:] + [0] + allowed += 1 + if sum(current) < m: + return allowed - 1 + return allowed + + def _init_history(self, task): + k = max(1, getattr(task._task_info, "k", 1)) + initial = None + if task.data and isinstance(task.data, dict): + initial = task.data.get("initial_k_sequence") + values = self._normalize_init_sequence(initial) + history = deque(maxlen=k) + slice_start = max(0, len(values) - k) + for value in values[slice_start:]: + history.append(value) + while len(history) < k: + history.appendleft(1) + return history + + @staticmethod + def _normalize_init_sequence(initial): + if initial is None: + return [1] + result = [] + if isinstance(initial, str): + for char in initial: + if char == "1": + result.append(1) + elif char == "0": + result.append(0) + elif isinstance(initial, (list, tuple, deque)): + for value in initial: + result.append(1 if value else 0) + elif isinstance(initial, bool): + result.append(1 if initial else 0) + elif isinstance(initial, (int, float)): + result.append(1 if initial else 0) + else: + result.append(1) + if not result: + result.append(1) + return result + + def _update_history(self, job): + task = job.task + history = self._k_histories.get(task) + if history is None: + history = self._init_history(task) + self._k_histories[task] = history + history.append(0 if job.exceeded_deadline else 1) + + def schedule(self, cpu): + if not self.ready_list: + return None + + key = lambda x: ( + 0 if x.running is None else 1, + -self._priority_distance(x.running) if x.running else 0, + 0 if x is cpu else 1, + ) + cpu_min = min(self.processors, key=key) + + job = min(self.ready_list, key=self._priority_tuple) + + if self._should_run(job, cpu_min.running): + self.ready_list.remove(job) + if cpu_min.running: + self.ready_list.append(cpu_min.running) + print(self.sim.now() / 1000000, job.name, cpu_min.name) + return (job, cpu_min) + + return None \ No newline at end of file From 6acf42c734d938fb95d4afe0baf3d67b2edc54fe Mon Sep 17 00:00:00 2001 From: Jonathan Tan MSI Date: Tue, 9 Dec 2025 23:44:42 -0600 Subject: [PATCH 7/7] Final code cleanup --- simso/schedulers/MK_DBP.py | 5 +++++ simso/schedulers/MK_EDF.py | 9 ++++----- simso/schedulers/MK_RMS.py | 9 ++++++--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/simso/schedulers/MK_DBP.py b/simso/schedulers/MK_DBP.py index ae79803..397c62e 100644 --- a/simso/schedulers/MK_DBP.py +++ b/simso/schedulers/MK_DBP.py @@ -1,3 +1,8 @@ +""" +@author Jonathan Tan (@jona1115 on GitHub) +@date 12/09/2025 +""" + from collections import deque from simso.core import Scheduler diff --git a/simso/schedulers/MK_EDF.py b/simso/schedulers/MK_EDF.py index b6c2460..ef320df 100644 --- a/simso/schedulers/MK_EDF.py +++ b/simso/schedulers/MK_EDF.py @@ -1,13 +1,14 @@ """ -Implementation of the Global-EDF (Earliest Deadline First) for multiprocessor -architectures. +@author Jonathan Tan (@jona1115 on GitHub) +@date 12/09/2025 """ + from simso.core import Scheduler from simso.schedulers import scheduler @scheduler("simso.schedulers.MK_EDF") class MK_EDF(Scheduler): - """Earliest Deadline First with (m,k)-firm requirements""" + """Earliest Deadline First (EDF) with (m,k)-firm requirements""" def on_activate(self, job): job.cpu.resched() @@ -20,7 +21,6 @@ def schedule(self, cpu): if t.is_active() and not t.job.is_running()] if ready_jobs: - ############### As far as I can tell this chunk is just selecting a CPU, so for uniprocessor, this chunk is junk ############### # Select a free processor or, if none, # the one with the greatest deadline (self in case of equality): key = lambda x: ( @@ -29,7 +29,6 @@ def schedule(self, cpu): 1 if x is cpu else 0 ) cpu_min = max(self.processors, key=key) - ####################################################### end junk chunk ####################################################### # Select the job with the least priority: diff --git a/simso/schedulers/MK_RMS.py b/simso/schedulers/MK_RMS.py index ca1e95d..bab402d 100644 --- a/simso/schedulers/MK_RMS.py +++ b/simso/schedulers/MK_RMS.py @@ -1,11 +1,14 @@ +""" +@author Jonathan Tan (@jona1115 on GitHub) +@date 12/09/2025 +""" + from simso.core import Scheduler from simso.schedulers import scheduler -from math import floor, ceil - @scheduler("simso.schedulers.MK_RMS") class MK_RMS(Scheduler): - """ Rate monotonic with (m,k)-firm requirements """ + """ Rate monotonic (RMS) with (m,k)-firm requirements """ def init(self): self.ready_list = []