Understanding ControlCore Task Scheduling

Understanding the tasking environment will allow the designer to layout a model that will execute more optimally.

Tasking Kernel

Models often have multiple elements of work that want to execute at the same time, but a system can only execute a finite number of work elements at any given time (one element of work per CPU). It is the responsibility of the operating system (OS), via its tasking kernel, to manage assigning the available CPUs to execute these different sources of work. The priority of execution for a source of work is used by the tasking kernel to make these decisions.

MotoHawk uses a real-time pre-emptive multi-tasking kernel that is housed within ControlCore.

Threads of Execution and the Context Switch

Both tasks and interrupts are threads of execution. A context switch defines the process where one thread of execution replaces the currently executing thread on a CPU. This replacement may occur because a thread needs to pre-empt another (because its priority is deemed to be higher than the currently executing thread) or because the currently executing thread wants to yield its execution (it does not want to execute anymore). The following example illustrates the context switch.

Consider that we have two threads, A and B. Each consists of an independent stack and CPU state (state of registers, instruction or program counter, etc...) that are operating on a module with a single CPU. Each thread executes its own linear sequence of instructions, which are determined by the logic under MotoHawk triggers (amongst other things). When the CPU needs to begin executing work from another thread (say B) it performs a context switch where the CPU state of the current thread (A) is saved and the CPU state for the new thread to execute (B) is restored. When a higher priority thread pre-empts a lower priority thread, the lower priority thread's state is saved and the higher priority thread's state is restored and it takes over execution. When the lower priority thread is given back the CPU, it simply restores it's state and continues executing from where it left off.

Control Core Task Example

Please note this diagram is not comprehensive and only illustrates a few core CPU components.  Also note that "linear sequence of instructions" does not necessarily mean that the instructions are sequentially layed out one after the other in memory, but simply that the next instruction to execute can be determined from the previous instruction.

Default Tasks and Priority

MotoHawk supports a number of default tasks that are configured by the Target Definition block. The default tasks are listed below from highest to lowest priority:

In addition to the default available tasks, MotoHawk also offers a Custom Tasking Bolt-On blockset that allows users to define their own tasks (effectively create their own threads). MotoHawk modules that make use of the MotoCoder technology can utilize this blockset.

Data Coherency

The tasking kernel's multi-tasking nature means that special consideration needs to be given to models that share data between different threads of execution. Pre-emption can result in unintended data corruption, which is discussed in detail here.

MotoHawk Triggers

A MotoHawk Trigger provides the mechanism to allow work to be assigned to a task for execution. Triggers can be thought of as events. MotoHawk abstracts most of its events (e.g. timer tick, angle event, etc...) through the trigger mechanism. All triggers execute within a task. Again, recall that a task is simply a container used to execute logic defined by triggers. A common misconception of triggers is that faster rate triggers will pre-empt slower rate triggers (e.g. FGND_RTI_PERIODIC pre-empting FGND_10XRTI_PERIODIC), but remember that only tasks pre-empt other tasks and there is no notion of pre-emption of a trigger. The behavior of triggers of differing tasks is illustrated by this example.

You can control which trigger executes first within the task by controlling the trigger's execution order. This is illustrated in the Queuing Trigger Example.

The MotoHawk Trigger block is one example of a trigger within MotoHawk.

Task Timing Example

Lets consider a simple example as illustrated in the figure below. Each block represents 1ms of "work" or CPU time. We have a total of 4 tasks each executing a single trigger and interrupts. The arrows represent the processor events (interrupts). Let us assume the following:

ControlCore Task Example

Queing Trigger Example

Now lets better understand triggers and how they execute. Think of a task as a queue that holds triggers, where the trigger event is linked to calling a block (or blocks) of logic (i.e. the logic inside your respective triggered subsystem). If you take a closer look at the MotoHawk Trigger block, you will notice it has a field for an execution order. This priority number does nothing more than control the order of how the trigger gets queued within the task, where lower numbers = higher priority. So for example, lets say we have two subsystems, where one is triggered at FGND_RTI_PERIODIC (base rate of 5 ms) and the other is triggered at FGND_2XRTI_PERIODIC (every 10 ms) and each have 1 ms worth of "work" or CPU time as shown below.

Please note that this is not exactly how triggers execute, but provides a simple visual demonstration where the end result is effectively the same

ControlCore Task Example

Now let's start at time t=0 and go to t=10ms and visualize the state of the Foreground Time Task "queue".

ControlCore Task Example
ControlCore Task Example
ControlCore Task Example
ControlCore Task Example
ControlCore Task Example
ControlCore Task Example

Again, please note that this is not exactly how things work. The intent of the visual demonstration is to provide the basic concept

Triggers Don't Pre-empt Triggers Example

Another common misunderstanding is triggers that occur more often will pre-empt triggers that occur less often or even asynchronous triggers pre-empt synchronous triggers. Recall though that pre-emption is only related to tasks; whereas triggers associate blocks of logic (triggered subsystems) for a task to execute upon notification from the relative trigger event. Let's consider a real-world example of a 4-stroke 4 cylinder engine running at a constant engine speed of 2000 RPM, where cylinder TDC events are equispaced occurring every 180°. We have a model with logic that is triggered in 4 different subsystems executing on a single core with the following configuration:

Task Trigger Type Execution Order Execution Time [ms]
1Foreground Angle FGND_TDC_EVENT Asynchronous 0 1
2Foreground Time FGND_RTI_PERIODIC Synchronous (5 ms) 0 2
Foreground Time FGND_2XRTI_PERIODIC Synchronous (10 ms) 1 1
3Background BGND_BASE_PERIODIC Synchronous (50 ms) 0 10

1,2,3Relative Task Priority (lower number ≡ higher priority)

Real-World Trigger Example

Starting at time=0ms, angle=0°, we have a RTI (real-time interrupt) event associated with the FGND_RTI_PERIODIC, FGND_2XRTI_PERIODIC, and BGND_BASE_PERIODIC time-based triggers as well as a TDC (top-dead center) event associated with the FGND_TDC_EVENT trigger. All triggers are ready to be scheduled; however only one can execute on our single-core system. The FGND_TDC_EVENT trigger executes in the highest priority task (Foreground Angle) and so gets scheduled to execute. After completion, the FGND_RTI_PERIODIC and FGND_2XRTI_PERIODIC are next to execute since the Foreground Time task has higher priority than the Background task; however note that the logic in the FGND_RTI_PERIODIC will execute first, followed by the logic in the FGND_2XRTI_PERIODIC since it has a higher execution order. After the logic in the Foreground Time task completes, the Background task is able to execute until it is pre-empted by the Foreground Time task again at time=5ms.

It should also be noted that triggers of different rates that execute in the same task like FGND_RTI_PERIODIC and FGND_2XRTI_PERIODIC need to complete before the fastest trigger of that group (FGND_RTI_PERIODIC in this case) is triggered again or that event will be late because FGND_RTI_PERIODIC won't pre-empt.

Task Starvation

Task starvation refers to the phenomena where a task desires to execute, but it is unable to do so because higher priority threads are executing in preference to the starved task. Task starvation is typically seen as a fault condition because it generally isn't normal for CPU utilization to remain at 100% indefinitely and therefore all tasks should be able to execute. Often task starvation is triggered by software malfunction. For example an executing thread that gets stuck in an infinite loop because of an algorithm failure or data corruption would cause task starvation. In this example the threads with an execution priority lower than the "stuck" thread will be "starved" of execution because the higher priority "stuck" thread will continue to execute in preference to them.

The Starvation Timer Definition and the Idle Loop Time Get blocks can be used to configure and monitor task starvation.