Anatomy of a Small Open-Source Kernel for DSPs
This article discusses dsp_K software, an open-source, real-time kernel and library for digital signal processors (DSPs). It covers both technical and historical ground, paying particular attention to the board support package (BSP) layer that is at the kernel's heart. I also address the use of open source within deeply embedded systems and hope to encourage you to consider such projects of your own, dsp_K-based or otherwise.
The source code and existing documentation that describes the dsp_K software can be found at the project home page. I also maintain a small collection of related bits, including some links to other DSP and Linux sites that you might find interesting (see Resources).
The purpose of the dsp_K project is to provide developers with a small real-time kernel suitable for DSPs, serve as an illustration of embedded open-source software, propose and implement an EL/IX profile for DSPs and serve as an exercise in porting pieces of Linux code to DSPs.
At the lower-most layer of dsp_K, the BSP aims to provide a set of runtime services designed to be as simple as possible and still constitute a functional software platform, while affording some personality. Staying small also makes it easier to port the BSP to other DSPs, while currently there is only one version for the analog device's SHARC family. Why the SHARC? Well, simply because that was the hardware available when I started the project. Actually, the term BSP is a kind of a misnomer because there is no specific board support; rather, the software can run on various boards. It might help you to think of it as a chip (family) support package.
The BSP can be sublayered, and at the bottom, sublayer 1-0 provides the minimal functional services required at runtime: interrupts including a PIT (periodic interval timer or tick) and basic task control and context switching (i.e., CPU sharing).
You can find the software that implements these functional services in the files dsp_Ki.asm and dsp_K.c. It may be useful for you to download them for reference as we go along.
Sublayer 1-1 consists of the kernel personality services, the main task functions and the scheduler sanity checker. This sublayer of the BSP personalizes the kernel, and the software that implements it is mainly found in the files dsp_Kt.c and dsp_K.c.
If you fancy starting a kernel-based project of your own, you can choose a personality other than tasks. For example, objects make your kernel be seen as an object manager but still use the functional sublayer below it. In fact, early followers of dsp_K could see such experimenting with the kernel personality through the dsp_MK (monolithic) and dsp_EK (experimental) kernels, now evolved into the layer 2 APIs monolith and process, respectively. It turns out to be constructive to present programmers with moderately different personalities through layer 2, based upon common kernel personality services in layer 1.
I will return to the nitty gritty of each of these sublayer BSP services later; now I want to introduce the second project objective. The dsp_K project is open source, which concerns both business and technical qualifications. As technical and open-minded people, we are sometimes asked by our professional colleagues how money can be made using the open-source model. It is a point we might discuss at length, but when asked (and with embedded systems in mind) I would probably start by suggesting good business might be made by providing proprietary products, service and support, complemented by development partnerships.
In the context of a dsp_K-based project, the benefits of a hybrid method of embedded software development for DSPs are simple to illustrate--proprietary applications also require the hardware on which they run, but the software kernel is a necessary nonprofit component for all concerned.
The third dsp_K project objective is to propose and implement an EL/IX profile for DSPs. EL/IX is a Linux-based, evolving open standard for embedded systems programming interface (see Resources). An EL/IX implementation is defined basically through a profile that identifies those POSIX components to be included. Through its wealth and profiles, EL/IX mainly targets processors with a rich hardware capability. But lesser profiles might also be provided for poorer cousins, like DSPs, which have a role in embedded systems. I hope that through providing an implementation, dsp_K can contribute to exploring the EL/IX specification for small profiles. We're registered as a nonelecting project on the EL/IX home page, accordingly.
The dsp_K software is designed to be a minimal application-specific embedded subsystem with no I/O, filesystem or network capability. Although it is possible to do otherwise, I recommend you consider functionally partitioning your dsp_K-based design to provide such peripheral functions elsewhere in your system architecture. A high-specification processor running RTLinux (see Resources), for example, can provide you with peripheral and user interface functions alongside a DSP running dsp_K (where the two could communicate using RTfifos).
In multiprocessor systems, and as engineers, we might think interoperability is key to good software design and would want to use a similar API across the processor subsystems. For this reason, the dsp_K project looks to EL/IX for guidance.
We live in interesting times with GNU/Linux real-time derivatives quickly being adopted by embedded systems designers. But there are many cases where embedding Linux doesn't make sense. You will undoubtedly have encountered Linux ports, like µClinux (see Resources), which is a subset for processors lacking MMU (memory management unit) support with reduced support in the programmer interface, such as fork. But being borne of x86 lineage, the Linux kernel weighs in heavily where we might consider ports to lesser devices like DSP cores. Sadly, it seems Linux is too rich for the SHARC and DSP cores in general (e.g., it would never squeeze on-chip in 10k RAM), so you can see why the dsp_K project does not attempt a Linux port.
How then, can the fourth project objective be satisfied? Well, Linux on a PC provides a handy program testing environment and good documentation in addition to making its source code available for reference. Linux also aims at POSIX compliance, which through EL/IX is a goal shared by dsp_K, so various components and supporting libraries can and have been ported (such as signals). From the embedded world, RTLinux has some useful modules that have been ported to dsp_K device drivers (namely fifos) that enable a host CPU to exchange data with a DSP across a dual-port memory interface. So, I like to say dsp_K satisfies the fourth objective by being Linux friendly.
Using DSPs in deeply embedded systems, we work mainly in a commercial world and wish dsp_K to be friendly with other host operating systems. As such, several additional sources besides Linux influence dsp_K development, notably classic monolithic operating systems (like vxWorks), process-oriented software design and simple PC-based kernels (e.g., uC/OS).
The dsp_K architecture is perhaps most influenced by classic monolithic operating systems like UNIX and its real-time extensions, especially VxWorks (the BSP file dsp_Ku.h compares to configAll.h). I recommend the UNIX man pages, which prove an excellent guide when writing libraries. Plenty of quality texts on UNIX-like and POSIX systems can be found on any academic publisher's web site.
There is no academic paper specifically about dsp_K, but the kernel embodies an approach to software design present in a research paper I had published a decade ago (certainly it's not worth obtaining, but see Resources if you're interested). In it, and based upon Hoare's model of communicating sequential processes, a process is comprised of concurrent threads that can run forever using their own scheduling mechanism. This is a specialization of process-oriented software design.
The process-oriented approach to software design, embodied by the Occam programming language, for example, is most visible in the dsp_K layer 2 process API. Both demand encapsulation, but contrasting with object-oriented design, especially as seen through C++, you will see the process-oriented model is one that encourages simplicity of purpose rather than genericness. Both models are useful ways to structure software applications, but dsp_K applications are more readily designed using a process-oriented approach where runtime and memory space are precious commodities.
You could write the simplest preemptive multitasking program with nothing but a PIT or tick. Consider the SHARC DSP and its shadow registers; the tick ISR could do nothing but reprogram the mode 1 register to flip onto the other of the two available register sets and perform an rti (return from interrupt instruction). By general consensus, to be considered an RTOS, a multitasking program should provide a few more features than only a PIT. An easy way to think about a desirable feature set is to play around with an existing, freely available, small PC-based RTOS, such as uC/OS, which is especially useful since it is free for noncommercial use.
I will summarize the history of the dsp_K project to date later, but as a teaser, the dsp_K project began in late October 1999, about a year or so after both the µClinux mailing list became popular and RTLinux papers were published. It was possible to run µClinux on the PalmPilot simulator under Linux and patch in and run RTLinux modules on PC-Linux. Indirectly, these projects influenced the structure, minimalism and features dsp_K provides and demonstrate how it might coexist with higher order RTOSes. It was further instructive to look at the mailing lists to see the issues people encountered when trying to adopt the new embedded Linux technologies.
Of necessity our discussion will center around the SHARC DSP implementation. But you might be interested in porting the BSP to other DSPs or wish to understand the general software design, so I'll try to strike a suitable balance.
As a reminder, the BSP comprises the minimal services required to provide a functional software platform for an application to run, along with a minimal kernel personality, interrupts, context switching, scheduler sanity checker and task functions.
You can find specific details about interrupts on the SHARC in the relevant Analog Devices manuals, which you should have anyway if you are going to develop an application for that target. Basically, interrupts are the mechanism by which the hardware notifies the kernel software of interesting events, for example a periodic tick. When one is raised, the hardware enters interrupt mode by pushing the program counter and status stacks into special-purpose registers and loads the program counter with a new value corresponding to the raised interrupt (commonly called the interrupt vector). The program (or kernel execution) continues from the new vector location. Although there are further details you can explore in the SHARC technical manuals, that is basically it. Summarized in Listing 1, you can also take a look at the dsp_Ki.asm file to see the interrupt vectors at the start.
The rti instruction is executed from within the kernel software to exit interrupt mode. When executed, the hardware pops the program counter and status stacks from the special-purpose registers it used earlier, and program execution continues from where it left off before the interrupt was raised. Interrupt-handling code is placed into a program code segment (commonly named seg_rth for runtime header) and is statically allocated in the correct memory location by the linker during buildtime.
Before enabling interrupts, you must program each interrupt vector with code (called an interrupt handler) that will run when the corresponding interrupt is raised. The interrupt handlers provided with dsp_K are found in the file dsp_Ki.asm and summarized in Listing 1. These handlers are statically compiled, but it is possible to reprogram them dynamically simply by writing instructions to the vector program memory address.
On the SHARC, each interrupt vector provides a four-program word space in which its handler runs. But for simple events that, say, require no action or set some flag, four words are enough, including executing an rti instruction. When the interrupt handling requirements are more complex, the handler must jump elsewhere in the program code to an interrupt wrapper. In Listing 1 you can see the handlers that jump to the dsp_K interrupt wrapper, DSP_K_INTR_RAPPER.
The dsp_K interrupt wrapper is also found in the file dsp_Ki.asm and is further summarized in Listing 2. It is carefully written to avoid overwriting any machine registers or memory that might be used by your application. Furthermore, the ustat1 and ustat2 registers are not assigned by the compiler, so the wrapper code and other parts of the kernel make good use of them. (This does mean you can't reuse them in your application code.) The ustat1 register serves dsp_K as a temporary general purpose register, and ustat2 serves to store the kernel state. You can view the bits (and their assigned meanings) used in ustat2 to record the kernel state in the comment at the top of the file dsp_K.c.
At the label DSP_K_INTR_RAPPER_1, the dsp_K wrapper switches to the SHARC shadow registers, then overcomes a bug in the SHARC simulator before jumping to the C function _DSP_K_INTR_RAPPER to complete the wrapper code. This second jump is taken for two reasons: first, C is easier to read, and second, the seg_rth segment is made to resemble a primary bootstrap of 256 program words, of which about half remain free after the fixed interrupt vectors have their share. The _DSP_K_INTR_RAPPER function, found in the file dsp_K.c, is statically linked into either the seg_krco or seg_pmco segment, each normally configured with several kilobytes of program space. This C function is basically a switch, where each case completes the interrupt handling requirements for a vector, and upon its return the wrapper executes the rti instruction to exit the interrupt.
The code surrounding the call to _DSP_K_INTR_RAPPER, preserving register i12 and the bugfix against the SHARC simulator, is provided to correct errant behavior. Such problems are BSP-specific and are found by debugging running code, so I can offer no specific guidance should you attempt to make modifications. The latter was discovered early on, perhaps in release 0.2, while the former i12 bug was found later, perhaps in release 0.4. Such problems just happen, usually manifesting in lockup or jumping to strange memory addresses, which you must overcome in the kernel.
The second functional service provided by the BSP is context switching. As with interrupts, you can find specific details about registers on the SHARC in the relevant Analog Devices manuals. As you probably know, context switching is the method whereby registers and related resources are preserved and restored as different tasks are run. A context switch may occur at various scheduling points, including both an explicit software request or a hardware event like the tick interrupt.
To carry out a context switch, the kernel copies the values in the CPU registers for the current running task to a save stack and then copies values from another save stack for the new task into the CPU registers. The kernel then exits, and the newly restored task runs until the next scheduling point occurs. There are further details for you to explore, but that is basically it.
All the dsp_K context switch code and related data structures are provided for you in the file dsp_K.c. They are placed into program segments (commonly named seg_krco for kernel code and seg_krda for kernel data) and statically allocated to the correct memory location by the linker during buildtime.
The kernel data requirements are small (and can be inspected by studying seg_krda in the optional .map file produced after linking). A structure variable called __DSP_K_context is maintained at runtime, and you can find its definition in the file dsp_K.h, which is also summarized in Listing 3. This main structure consists of a t__DSP_K_tcb substructure per task, in addition to a small number of kernel-specific variables. These kernel variables include the current task and a pointer to the kernel environment comprising the task list supplied when your application main function called DSP_K_RUN, along with the arguments supplied to main from the C startup library.
Each t__DSP_K_tcb substructure stores the data needed to support one task. Basic data includes the task's group ID and parent ID along with any timer or wait events pending. Each task also stores its own exit value and errno for inspection, along with optionally built-in runtime statistics as a primitive profiling capability. The key element for your interest, however, is the array of ctxt integers that are used to save the context (or state) of each task in your application.
You configure the task context to be saved at buildtime, subject to a minimum base. The minimum save requirements are the general purpose registers, the general purpose index registers and the status and stack checking settings. Other SHARC registers can be saved optionally, depending on your application requirements, to include the modify, base, length and multiplier register sets. But often you don't need them, and runtime performance is improved by eliminating them from the context switch function.
Like the kernel context data, you can find the context switch function in the file dsp_K.c, and it is called _DSP_K_SWITCH to avoid any potential confusion. It is first called from DSP_K_RUN to start multitasking and then evermore from DSP_K_TASK_SCHEDULE until your application ceases multitasking and returns via DSP_K_RUN to main. There are basically two routes through the kernel to reach DSP_K_TASK_SCHEDULE: either directly from a task function, such as DSP_K_TASK_PEND, or from the tick ISR. The context switch function behaves slightly differently depending upon which route is taken.
Wrapped up in C, the context switch function is mainly written in assembler (a summary in Listing 4 omits the assembler detail). It first performs some task lock checks and decides if a switch is actually necessary. If so, it gets a pointer to the current task context data structure described above and updates it from the RUN to the READY state (i.e., logically moves the task back onto the ready queue). It then sets about saving the current task context. Not shown in Listing 4, if the ISR route was followed, the code walks up the stack searching for the task frame before the interrupt was taken and flips the shadow registers used by the kernel in order to retrieve the task state.
With a little bit of juggling, saving the current running task context is fairly straightforward. Essentially, the general purpose registers are copied onto the current task context data structure followed by the general purpose index registers. Juggling is necessary to preserve registers until they are saved, to work with the shadow registers if the ISR route is followed and to collaborate with the compiler. (Depending upon the version of compiler you have, the executable code differs subtly, and you should make sure it behaves sensibly where clearly commented with VDSP_VER.) After saving the index registers, any of the optionally configured multiplier, modifier, base and length register values are copied onto the current task context stack, completing the first half of the context switch job.
The second half, recovering the new task context so that it is moved from READY to RUN and thus made current, is likewise fairly straightforward and works essentially in reverse of a context save.
Then in the third half, a C stack frame has to be set up. This is fiddly for two main reasons: first, the CPU registers have to be prepared in an orderly fashion, and second, the registers recovered in the second half must be preserved. (Again the details are omitted from Listing 4, but you can see them in the dsp_K.c file.) To prepare the CPU registers on a SHARC, the PC stack has to be emptied, the status and arithmetic status registers need to be set and the mode registers programmed in addition to setting up the PC, FP and SP registers. Finally, an ordinary return is executed to exit the context switcher and jump onto the new task frame.
Still with me? Well, we've just about covered the lowest sublayer functional services and can now move up a bit to the kernel personality services.
One facet of the process-oriented approach used in dsp_K is that tasks run forever (until explicitly halted by a call to DSP_K_TASK_EXIT or _exit), and the DSP_K_TASK_RESET function attends to this. A task stack is established in DSP_K_RUN so that, on reaching the end of its entry point code, your application tasks return to the DSP_K_TASK_RESET function. All this does is reset the task context to its initial conditions, and like the end of _DSP_K_SWITCH, it sets up a C stack frame and returns into the task entry point function.
Because tasks run forever, a mechanism for scheduling them needs to be provided. As you might have noticed when I introduced the BSP services above, the kernel services do not include a common scheduler. Rather, in the dsp_K model, task scheduling is carried out through programmer-provided functions attached to each task (refer to the t__DSP_K_TASK_DSCR.scheduler element in Listing 3). The dsp_K distribution includes classic round-robin and priority scheduler functions as generic scheduling methods, but you might want to write your own. The kernel uses the DSP_K_TASK_SCHEDULE function to call programmer-provided scheduler functions and then to check the sanity against preset limits so that the kernel won't crash before calling the context switcher.
With traditional UNIX or POSIX systems you can start tasks dynamically, so an unpredictable system lifetime exists for the kernel. By contrast, many embedded systems are designed for a specific purpose and to function statically. It is often well-stated that real-time schedulers should provide a hard real-time response to events (i.e., possess a low-interrupt latency). We could also consider that in order to build a predictable embedded system, deterministic scheduling is needed. The purpose of a scheduler function is to enable each task in your application to decide which one should be run next, and it is possible to provide a different scheduler function for each task within a dsp_K application. It is always possible for us to write poor applications that yield non-deterministic scheduling, but such design flaws are in our application code and not in the kernel.
A second kernel personality service extends the functional services with a family of task functions. These add to the BSP so as to provide you with a means of detailed application task control. At runtime, all tasks are in some state, and there is a set of corresponding state functions provided, which you can see in dsp_Kt.c. In addition to a state, tasks have attributes, and there are task functions to retrieve or set these. Furthermore, resource-oriented functions attach system resources to tasks.
A runtime dsp_K task notionally exists on some kernel queue. Those tasks ready to be run live on the ready queue, others pending some event reside on the pending queue, and those that have terminated are found on the null queue. While some kernels do indeed support queue structures, dsp_K uses a state variable such that one attribute of a task is its state (because this requires less memory). The list of possible states is an enumeration found in dsp_K.h (and near the top of Listing 3) but includes DSP_K_STATE_READY and DSP_K_STATE_PEND, for example. For each state a task can reside in, there exists a corresponding task function like DSP_K_TASK_READY. The purpose of the state functions is to provide your application tasks with a way to set their own or others' states and then to call its own scheduler function.
Where the state functions are used to set values, a family of attribute functions are used to get values. You can see the task attributes in the t__DSP_K_tcb structure within dsp_K.h and they include errno, parent and group (see also Listing 3). It follows, for example, that the attribute function DSP_K_TASK_PPID retrieves the parent task number for the calling task. The DSP_K_TASK_ERRNO function differs a little from the other attribute functions in that it both sets and returns an errno value for the task and is often used in library code like this:
return(DSP_K_TASK_ERRNO( -EIO ))
By resource functions I mean those that temporarily assign system resources to a task. For example, you can use DSP_K_TASK_TIMER to attach an available timer. Once the timer expires or is canceled, it is returned to the pool of available timers and available for use by another task. These resource functions might be used to support higher layer library functions.
Similar to the UNIX function with which you are probably familiar, you use the library function fork() to spawn a child task. It is supported in dsp_K through the BSP task resource function DSP_K_TASK_CREATE. When compiled, your application is configured with a fixed number of tasks, or more specifically, a number of task slots. Although dsp_K applications are statically configured, it does not follow that all task slots have to be initialized with a function, and some may be left empty. Furthermore, after time some tasks may exit and so release their slot resource. The task create function searches for a free slot, and if one is found initializes it with a new task (entry point) to run.
After the task control block structure is initialized with a task, the dsp_K version of fork goes on to copy the parent context and stack, adjusting pointers onto its own stack so that it will run independently. Once copying is finished, the parent scheduler is called, and it may be that the child runs before the parent or vice versa.
So, to summarize, we have seen that the BSP comprises a set of functional and personality services that together provide a platform to build upon in constructing an application. Through highly scalable system configuration settings and selected layers of support software, such as POSIX-like libraries, we are able to build a highly tailored application.
Together with your application code, the kernel is statically configured at buildtime. Therefore, exactly which hardware target resources are required to run the combined program are known. Configuration will fix the number of tasks and the tick priority among other settings. The BSP includes a file, dsp_Ku.h, that undefines/defines all possible settings; this was influenced by the configAll.h VxWorks header file. A key issue with DSPs especially is that there is limited resource availability, and you need to allocate those required at buildtime. By way of example, all but example4 (heap memory management) in the dsp_K generic sources subdirectory are built to run on chip in a few kilobytes of memory.
You tailor each application by inclusion of a custom configuration file called dsp_Kcfg.h; Listing 5 shows an example. You explicitly set only those required buildtime variables to allocate the resources needed by your application. You do not need to set unwanted buildtime variables because the dsp_Ku.h file takes care of them for you. Your custom configuration file is included at buildtime by the BSP, in addition to any API and higher-layer libraries used, so that the combined binary includes only those elements needed to run your application. For example, if you omit the DSP_K_CONTEXT_MULTIPLIER variable then the context switch will be made smaller by not saving and restoring the SHARC multiplier registers. You can even disable core BSP settings, like the tick rate, through DSP_K_KERNEL_TICKS, thus reducing an application to a cooperative multitasking one, showing the full configurability and scalability of dsp_K.
The BSP provides the programmer with a layer 1 programming interface. When configuring your dsp_K-based application, you arrange layers of software on top of it to extend the programmer interface. This is done simply by including the required software libraries in your project settings so they get compiled and linked in, and by setting the required configuration options in dsp_Kcfg.h. Layer 2 API libraries extend the kernel personality, providing functions such as intertask message passing. Next up, the device driver layer 3 enables your applications with access to hardware and protected software elements like signals. Topmost at layer 4 are libraries that provide you with a familiar POSIX-like programmer interface; these are our EL/IX implementations.
You may gain a deeper insight into dsp_K-application configuration and layering through the generic examples and documents provided with the distribution.
I would now like to address how you might develop using the open-source model and what the open-source dsp_K license requires.
As proposed earlier, a hybrid development using both open- and closed-source software can work well, where the dsp_K software would form one open-source element. A vendor might provide you with a development board or reference design along with a port of dsp_K, which you might tailor and extend to suit your application. Mainly, this would simply involve modifying the dsp_Kcfg.h configuration file, but you might also write nonapplication-specific libraries or modify the kernel to exploit certain desirable features to fix bugs. You might choose to give modifications to the vendor for inclusion and support, remembering that in addition to your love for open-source engineering, money is made from products and services rather than the kernel.
Where do you start? First, you need to download the dsp_K software and documentation. Currently there exists a BSP only for the SHARC DSP, and furthermore this is integrated with the Visual DSP (VDSP) toolkit from Analog Devices, the company that makes these chips. In fact, there are old GNU tools for SHARC that you could try using (see Resources), and I'd love to hear your experiences if they work with dsp_K.
The second step is to try compiling the examples downloaded as part of the distribution. Using known-to-work software helps to establish confidence in your build environment. You might then try modifying one of the board-specific flashLED examples for your hardware. Once you have these running, you will be reasonably familiar with the software and can decide whether to continue using it for your project.
You are encouraged to modify the kernel to suit your own application needs. Indeed, you may well need to modify or extend the dsp_K software in order to provide required functionality on a particular target board or to extend or write a new API or device driver. The kernel must be capable of being built and run in various configurations, and as you add new kernel features, you can maintain confidence by performing regression tests. The generic source examples are a good way to do this, and you could write new ones to test your new features and libraries.
What if you choose to participate in the Open Source community by giving back your contributions? Or, what does the license require? First, read the license included with the downloaded distribution; it's not too long, and your obligations are lightweight. The dsp_K project allows you to choose the LGPL instead (see Resources), should you not wish to make use of the extra privileges afforded.
The extended privileges afforded by the dsp_K license aim to provide you and your business colleagues with a stepping stone, bridging the commercial world of deeply embedded systems with the Open Source community. The hybrid method described earlier captures this bridge practically. In fact, the LGPL probably allows similar privileges as our license, but this isn't made clear. The bottom line is that dsp_K is open source, and you must tell this to people to whom you distribute software based on it. Like you, they have the right to look at it and change it and the right to use a modified version with your (proprietary) application. You are neither obliged nor could you be reasonably compelled to give away your proprietary software.
The version history of dsp_K is formally briefed in the document dsp_Ksrc.htm. Each major version represents a milestone in the kernel evolution.
Version 0.5.0 marked the first open-source release--on March 18, 2000, dspEK was announced on the comp.dsp newsgroup. The dspEK software continued to be minimally evolved and tested for subsequent 0.5 versions, but a new monolithic version, dspMK, was begun to create an open-source flavor of the proprietary dspOK and to include device drivers. Developments advanced modestly, and dspMK was released over the course of the following two months. Sharing a common core between dspEK and dspMK, the notion of APIs and the BSP as layers emerged. Consequently, the experimental tag became undesirable, so dspEK was deprecated by the short-lived but better named process flavor dspPK.
At the start of June 2000, the BSP and API layering had become clear, and the common kernel was folded into the project basename, dsp_K, creating version 0.6. While they persisted for a while, the API names dspPK and dspMK became process and monolith respectively. Seeking further development direction, integration with Linux and the Red Hat proposed EL/IX standard was identified as the basis for this work, which continues at this time, although at a modest pace, firmly establishing the software layer architecture with the BSP as a firm foundation.
"Rigourous approach for modeling and refining concurrent behaviour in JSD specifications", J. Rose. Structured Programming Journal 13(2), Springer Verlag, 1992.
dsp_K Project Home Page: dsp-k.sourceforge.net
dsp_K Working Mirror: www.jhrose.dial.pipex.com/dsp_K.htm
GNU SHARC Tools: ftp.analog.com/pub/dsp/210xx/dev_tools/3.3
Julian Rose is both an Englishman and a contract programmer. He started programming on Z80 systems as a teenager around 1978.