[Blog] Demystifying the PsyC language
So, you’ve heard about our technology called ASTERIOS®, and you’ve heard that it can “generate an optimal real-time scheduling in a few minutes, target a multi-core platform as easily as a single CPU, guarantee the determinism by construction, provide strong safety and security evidence by calculation and validate the temporal behavior of your system by simulation” (from the Product overview page) – we’re still working on the return of the loved ones. But, you’ve also heard that our tools are based on our own “formal” language to specify the behavior of your application, and maybe you think that you need to learn a completely new paradigm. The good news is, you don’t, and here’s why.
Real-time systems on modern processors: time is of the essence
When designing a real-time system, the designer has to explicitly state the hypotheses that have to be verified in order for the system to behave according to its functional specifications. This has already been presented by my esteemed colleague in a blog post (Settling the Time- vs. Event-Triggered Debate). To master such systems requires a complete understanding and a thorough description of the temporal and logical behaviors. As such, it is not acceptable that implementation elements lead to changes in or precision to these specified behaviors.
Back in the good ol’ days, the execution of a program was deterministic, i.e. the dynamic behavior of the system was invariant in time. It is well known since the early 90’s that the hardware manufacturers have “improved” the CPU’s: superscalar processors, branch prediction, multi-level caches, out-of-order execution, DMA’s… When the hardware hides the non-deterministic aspects for the software part, the issue that remains is the asynchronous interaction between software components. Conversely, for the majority of current single-core processors (e.g. as soon as they have a DMA) and almost all multi-core processors, then interference and asynchronisms have to be taken into account into the design, like in the diagram below:
If this is not possible, test cases will have to be developed to cover all the combinations of interactions between all the sources of interference, assuming that they can be identified and their number is finite and small. In practice, this often leads to the under-exploitation of the hardware by disabling as many interference sources as possible (no DMA, limited peripheral access, cache flush every task context switching…and then full reload when restarting a task) and as a consequence to many performance losses.
OK, now in plain English: when the hardware does not help, the software designer has to do all the hard work. There are no standard languages to express these architectural considerations, and in most cases the underlying programming language used (let’s face it, we’re talking about C here) does not provide any means to express the concurrency of functions, and in particular the concurrency of hardware elements with the software. So we decided to create our own language to express this.
You need a language to deal with parallelism and asynchronisms. Trust us, we’re engineers
In the beginning, Sir Charles Antony Richard Hoare created CSP. And he saw that it was good, because the theory was sound and applicable, but the principle was by design asynchronous: the communication between tasks in the systems is described only as messages exchanged between them. In the industry, Ada has taken a lot of concepts from CSP, in particular the rendezvous mechanism and the selective wait (https://en.wikibooks.org/wiki/Ada_Programming/Tasking).
CSP is great to describe concurrent tasks, but for real-time systems, there is major drawback: no way to express a synchronous exchange of tasks, i.e. an exchange that occurs at a given instant in time. For example, how to express formally (i.e. the meaning is non-ambiguous so it is possible to create a tool that understands it) the need to communicate every 5ms +/- 1ms? Or that a task has a period of 10ms, producing data to a task of period 15ms?
Usually, the specification document contains requirements written in natural language, and then a designer creates tasks with the right amount of sleep time (sometimes managed by CPU time slots) to ensure the right periodicity, and when two tasks communicate with each other, he places the lock mechanisms that are required to prevent concurrent access to the data. Well, we all know what happens in the end: once this design is done, it is very difficult to test it (in particular in multi-core, where locks are complex and effects are difficult to handle) and it is practically impossible to change anything later on (e.g. to slow down one of the tasks). It would be nice if you could express unambiguously the fact that a task synchronizes with another task on that instant in time, or that its period is so or so, and then a tool would produce the configuration of the real-time kernel for you.
Enter the Psy language, or Parallel Synchronous language.
The PsyC language
Created in the 90’s by Vincent David and his laboratory at CEA (Commissariat à l’énergie atomique et aux énergies alternatives – French Alternative Energies and Atomic Energy Commission) and included in the OASIS technology, the idea was to propose a language that describes synchronous exchanges of data between tasks for nuclear qualified systems. And since most real-time software programs are written in C and a large number of cross-compilers are available for this language, it has been decided to build a PsyC language. In theory, nothing prevents us from doing the same job from other languages such as Ada, C++… For the moment, we have decided that calling external code using external bindings from the PsyC is enough, but this could evolve in the future.
The PsyC is a combination of the C language for all functional aspects (operations, functions, etc.), so that it is easy to include legacy code and the programmers don’t have to learn a new language for this aspect, and of the Psy language, a grammar which adds the time and concurrency elements required to formally define all aspects not managed in C.
In a previous blog post, we illustrated the time-triggered approach and provided an introduction to the concepts of deadlines and earliest start dates. To summarize, a task in Psy is a sequential execution unit, and it is similar to a main function in C. Since in C the user cannot declare more than one main function in an executable file, we had to introduce a new keyword: agent. Conceptually, agent defines a main function, with all the consequences: global variables are accessible only to this main function, and to communicate with other agents, the user has to use Psy communication means, like pipes between processes. Think of an agent as a complete executable by itself, we only provide a convenient way to create several executable at the same time and to communicate between them.
Then we need to define when a treatment should start (earliest start date) and end (deadline). We introduced the keyword advance to create a synchronization point with a universal time reference (universal for all tasks in the system). So we need this time reference, which comes as a source, which provides a series of formal ticks, and clocks, which enables the user to define periodic time bases on which the tasks may synchronize (it does not mean that the task has to be periodic, it may synchronize with one clock and then another one, defining very advanced time patterns).
Last but not least, most tasks in real-time systems never end (they loop infinitely), so we provide a convenience for the description of the top-level usual while(1) loop: a body. A body is simply a infinite loop, and you can go from one body to the other easily, defining some states machines in your code.
So, in the end, how do you define a task that produces data every 5ms +/- 1ms? Here’s the solution:
Maybe a diagram will help (the basic diagram was generated with our tools, only the annotations have been added later on):
So the code above will behave exactly the same way on any target that supports real-time at 1ms, and if we define other agents that are executed concurrently (or even better, in parallel if the target has the capability), it will continue to execute exactly as specified as long as the CPU is powerful enough (we don’t deal in miracles: the CPU has to execute the instructions in the end…). So one major advantage of the formal language is that we are able to compute this formal temporal description and configure automatically the real-time kernel so that it behaves as required.
As a side note, independently from the timing aspects, we have two communication means available: data flows, called temporal variables, and on-demand messages in FIFO’s, called streams. These require simple get/set functions to access them, and the difference with traditional read/write functions (such as POSIX send and recv) is that they include the timing aspects. This blog is not a complete course about PsyC, please do not hesitate to contact us for further details if you are interested.
See, that was not so hard after all
In Ada, when they decided to implement the concepts of CSP, they added several keywords to the old general-purposes languages: task, rendezvous, select, etc. We did the same but with C, with a major addition: time.
It’s difficult to provide a comparison with other language as the concepts we added do not exist in them; however we kept the language as simple as possible. PsyC is built over C to ease the training of programmers and to keep as much legacy code as possible, and its syntax is close to C also.
Also, in the long run, we don’t want the user to write the PsyC manually, not because it is complex, but because we intend to provide additional tools that will generate the PsyC code from a high-level description of the system (functions groups, mixed-criticality tasks groups, response times, etc.). We would like the user to focus on the expression of his needs (design choices, data coupling between tasks, …), not how to implement them (how to code).
In the end, the Psy language has been created formal because it enables us to propose a compiler and checker tools. Formal does not mean “hard” mathematics or disconnected from reality. It has been created by real-time experts with a deep understanding of the hardware issues that prevent the software design from being truly concurrent and all the consequences in terms of parallelism.
About the author
Adrien BARBOT is Product Architect at KRONO-SAFE, with 15 years of experience in development of safety-critical software and systems, mainly in the aeronautic domain (DO-178/ED-12 DAL A, B and C). He has led projects at Axlog Ingénierie for Thales Avionics and Zodiac-Intertechnique as a Software Team Leader, and at Messier-Bugatti-Dowty (now Safran Landing Systems) as Avionics Manager. During that time, Adrien has participated in the certification of several systems from DAL C to DAL A (electrical functions, LGERS, braking & steering system), and in the qualification of the tools used during the development. Since 2012, he is in charge at KRONO-SAFE of the definition of ASTERIOS® product family. He also leads the development of ASTERIOS® Checker, the tool suite provided as a DO-330 qualification kit for ASTERIOS®.