Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Parallel

Unix, Ada, and Real-Time a Forth Multitasker


JUN91: UNIX, ADA, AND REAL-TIME - A FORTH MULTITASKER

Jack is a senior project manager for Vesta Technology. He is a contributing editor to Embedded Systems Programming magazine, and a member of the ANS/ASC X3/X3J14 technical committee for ANS Forth. He can be contacted at 7100 W. 44th Ave., Suite 101, Wheat Ridge, CO 80033, as jax@well. UUCP, or as JAX on GEnie.


Although C is a popular language for programming embedded systems, Forth is more productive. In fact, Forth can make LEDs flash and stepper motors grind while C programmers are still setting up compiler environment variables. Forth offers interpreter convenience and compiler execution speed combined with a modularity of structure paralleled only by hand-tooled assembly code.

One of the most striking conveniences of Forth as a control programming language is that almost all commercial Forth systems and most shareware and public domain Forths have a multitasker. Multitasking allows neat modularization of asynchronous activities in an embedded system. An example of using a multitasker in a control application is to create three extra tasks in addition to the foreground console task: a data-collection task; a motor- (or whatever) control task; and a task to handle second serial channel communication to another processor. All three tasks can run in the background while the programmer debugs or the operator directs the system via the Forth interpreter or canned application interface, which continues to run in the foreground.

Forth Multitaskers

The familiar Forth multitasker depends on a chain of well-behaved and cooperative tasks containing embedded PAUSEs that voluntarily relinquish the processor in turn. I described the advantages and disadvantages of this sort of multitasking in the article "Cooperative Multitasking" (Embedded Systems Programming, April 1990).

In the same issue, Phil Koopman, formerly senior research scientist for Harris Semiconductor and world-renowned authority on Forth-based CPUs, took the idea several steps further and discussed this and more intricate models of Forth multitasking, grouping them into four categories: lightweight, mediumweight, heavyweight, and preemptive. His examples, understandably, involved the Harris RTX2000, an incredibly powerful "Forth engine" based on the legendary Novix NC4016 designed by Chuck Moore, the inventor of Forth.

Recently, I completed an onboard ROM Forth system called "Forth-83i96," that produces ROM-able autostart object code for the Vesta Technology SBC196, a single-board computer based on the Intel 80C196KB/KC. Forth-83i96 possesses a classically efficient Forth cooperative round-robin multitasker. Nonetheless, after delivery of the system to Vesta Technology I remained intrigued by the mixed-multitasking models described by Dr. Koopman. In a day's work I was able to superimpose an optional preemptive multitasker over Forth-83i96 as an application program. I had hoped to do so without reference to any information beyond that available to an SBC196 user adept at the art of reading the fine manual.

Alas! The way I designed the original multitasker precludes my utopian dream: It turns out that "insider information" is required to complete the program, specifically, awareness that the Forth-83i96 round-robin multitasker restart routine presumes that register 0x26 of the 100-byte 80196 register file is loaded with the address of the instance of the user variable ENTRY local to the task about to restart. One positive result of this experiment is that I have decided to change the internals of the Forth-83i96 multitasker to make it simpler for users without access to the system source code to hack the multitasker along the lines presented here. K.I.S.S. (Keep It Simple, Stupid) is still the watchword in Forth.

PREEMPT96.F

The code in PREEMPT96.F (Listing One, page 98) provides an environment that falls in the cracks between Phil Koopman's description of "mediumweight" and "heavyweight" multitasking. I call my effort "medium-heavyweight" because it provides for preemption at any point when a timeslice has run out, while allowing the program to avoid the additional overhead of the preemptor by voluntarily PAUSEing in the traditional manner before the timer interrupt decides to "lower the boom" on the languidly lingering task. PAUSEing cooperatively is a great cycle-saver, as Forth's PAUSE only occurs between Forth definitions when all the state of the Forth virtual machine is in a very few registers and mostly on the dual Forth stacks. PREEMPT, on the other hand, must save a large register file, because the timer interrupt has no way of knowing at what point in its processing the Forth virtual machine is at the time of preemption.

The preemption scheme embodied in this application does not replace the onboard cooperative multitasker, but rather supplements and "watchdogs" it. Most of the facilities of the underlying cooperative tasker are still present. The core of the preemption engine is the interrupt service routine ?PREEMPT. ?PREEMPT is driven by a periodic interrupt on the processor Timer1. ?PREEMPT actually reloads Timer1 at every interrupt with the value contained in the PERIOD register object. Timer1 counts up and interrupts at zero, so PERIOD must contain the two's-complement of the desired count. (One count is eight processor state times.)

When ?PREEMPT fires, it compares the User pointer (the register with which the system's cooperative multitasker points to the task currently executing) with the User pointer value that was present the last time ?PREEMPT fired. If the User pointer value has changed, ?PREEMPT reloads its saved copy (LAST-UP) for its next comparison. Also, the register object CLICKS is loaded from the task-local priority variable of the new task in the line: clicks 'my-pri >body @ up [+s] ld. If the User pointer is unchanged, ?PREEMPT decrements CLICKS and branches on nonzero to an interrupt return. If CLICKS expires, ?PREEMPT branches to PREEMPT, which saves a critical portion of the register file and a few other data objects and passes control to the next task. Seventeen items go to the data stack in this operation. Forth-83i96 provides a data stack depth of 256 items, so average applications should be safe.

When PREEMPT arbitrarily suspends a task and goes to pass control to the next task in the round-robin, it recognizes those tasks which it itself did not suspend previously. The test is the line: ax reclaim # cmp, in which PREEMPT tests the contents of AX (which contains the vector fetched from the ENTRY user variable of the task about to be restarted) for a match with the address of the RECLAIM routine, the routine that restarts a preempted task on the next cycle of the round-robin.

When the restarting task does not PAUSE voluntarily, but is PREEMPTed, PREEMPT pushes the vector resident in the task-local instance of ENTRY at the time of preemption onto the stack in the line: 'ENTRY >body @ up [+s] push and replaces it for this cycle of the round-robin with the address of RECLAIM. As it restarts the task, RECLAIM restores the contents of ENTRY in the line: 'ENTRY >body @ up [+s] pop so that the task can proceed to its next voluntary PAUSE without ever knowing it has been preempted.

If, upon testing, ENTRY is found to point to RECLAIM, PREEMPT passes control to that routine. RECLAIM will restore the Forth virtual machine, including the User and stack pointers and much of the 80C196 register file. RECLAIM will also pop back the processor flags (via a POPA instruction) to their state before the timer interrupt which PREEMPTed the task.

On the other hand, if ENTRY points to any other routine, PREEMPT does not care what the routine is. PREEMPT merely prepares to pass control to that routine (typically, the system cooperative multitasker restart routine that I call RESUME, or for a SLEEPing task, the routine PASS). Because of PREEMPT's "don't care" behavior, the Forth-83i96 cooperative multitasker word SLEEP still can shut down a task, because it plants PASS in ENTRY. (Unlike the situation under the cooperative tasker alone, after a task has been shut down via SLEEP, it must not be recalled via WAKE, as WAKE cannot know the method by which the task was suspended; the only clue was the contents of ENTRY that were overwritten by SLEEP. "The remedy is left to the reader as an exercise.")

PREEMPT must take one specific action on finding that a task will be restarted by a routine other than RECLAIM. PREEMPT must restore the processor flags to some state, because the current ones were pushed by the PUSHA in the first line of the timer interrupt ?PREEMPT. PUSHA clears the interrupt mask, so simply ignoring the situation is not an option. On the other hand, PAUSE does not save processor flags, so what to do?

To this end, in SETUP we save the processor configuration to the register object SAVE-STAT after the timer interrupt has been enabled. The WSR, INT_MASK, INT_MASK1, and PSW saved therein must be adequate to the restarting of any task from a voluntary PAUSE. This pretty much precludes any task operating under the medium-heavyweight multitasker from trifling with register windows or interrupt masks, (although uninterruptible sections bracketed by DI [Disable Ints] and EI [Enable Ints] are still possible, as long as consideration is given to possible timer rollover during suspension of interrupts). Of course, in any other preemptive multitasking operating system it is equally atypical for tasks other than the system task to change interrupt masks.

It does not matter that the condition flags in the Processor Status Word (PSW) such as CARRY and ZERO, which are arbitrarily "restored" to a PAUSEed task by PREEMPT in this manner, do not match the condition flags that were present when the task PAUSEed. PAUSE is a Forth definition that executes between other Forth definitions. Forth definitions pass state information from one to the other via data stack entries. Only PREEMPT (which can interrupt the internals of a Forth code word while that word is performing register-to-register calculations) must restore precisely the condition flags that were saved.

PREEMPT does not really save the whole state of Forth-83i96. Faced with a huge register file, I went hog-wild in coding the original system and cached data normally consigned to VARIABLEs in registers. System objects having to do with, for instance, the operation of the resident 80196 assembler, are actually registers and are not saved by PREEMPT. Therefore, the preemptive multitasker cannot safely be active at compile time.

I have arbitrarily truncated the set of registers saved and restored by PREEMPT to include only the Forth virtual machine registers and the list of scratch and iteration registers reserved and documented for user applications. This seems to work satisfactorily; the user is welcome to change the code to preserve more state.

To use the medium-heavyweight multitasker, all compilation should take place before the multitasker is activated. Tasks may or may not contain PAUSEs, as illustrated in SAMPLE-TASK0 and SAMPLE-TASK1. The priority must be set for each task (SET-PRI), a period set for the timer interrupt (SET-PERIOD), and SET-UP invoked.

Next, each task must be wakened for the first time via the system round-robin multitasker word WAKE. MULTI, however, only affects PAUSE operations, so its use becomes optional. Once SET-UP is invoked, the preemptive multitasker is off and running--until reset! Use SLEEP to shut down an undesired task, or better yet, lower its priority (which may be changed at any time) via SET-PRI.

In the context of the 80C196 micro-controller, the medium-heavyweight tasker is a toy. The principle is applicable, however, to such processors as the Philips/Signetics 68070, a control-oriented 68010 workalike whose onboard memory management unit could offer considerable practical employment to mixing preemptive and cooperative multitasking in the Forth programming model.

Note on the Forth Assembler Syntax

Forth systems traditionally contain a resident assembler, complete with structured conditionals such as DO ... LOOP IF ELSE THEN BEGIN UNTIL, and so on. A Forth assembler is suitable for generating native code for any assembly construct from an interrupt routine to a hand-coded Forth definition. Such assemblers are effectively themselves small Forth programs, and as such obey the parsing logic of Forth, which dictates that operands precede operators. (Although many PC Forth systems now also feature standard macro-syntax assemblers, the overhead for such an assembler in a tiny controller Forth is prohibitive.) Addressing mode indicators are themselves cast as individual operands to the assembler operators: #, [], []+, [+s], and so on.

Thus, if the timer interrupt ?PREEMPT (see Listing One) were recoded from Forth "reverse Polish" syntax to macro assembler syntax, you would see the code in Example 1. The strange syntax by which variables and user variables are referenced in the Forth assembler has to do with the nature of the ROM-able code generated by Forth-83i96. Both variables and user variables possess an embedded offset: In the case of variables, it is an absolute address of their data body in distant RAM; in the case of user variables, it is the offset from any task structure address at which the data body of their local instance is to be found. The phrase: '<var-name> >BODY @ returns the relevant embedded value in both cases.

Example 1: Recoding the timer interrupt ?PREEMPT from Forth "reverse Polish" syntax into macro assembler syntax

  QPREEMPT
    PUSHA
    LDB WSR, #0FH
    LD  TIMER1, PERIOD
    CLRB    WSR
    CMP UP, LAST-UP
    JE  ONWARDS
    ST  UP, LAST-UP
    LD  CLICKS, MY-PRI[UP]
  ONEMORGO
    POPA
    RET
  ONWARDS
    DJNZ    CLICKS, ONEMORGO
    SJMP    PREEMPT


_A MEDIUMWEIGHT-HEAVYWEIGHT FORTH MULTITASKER_
by Jack Woehr


[LISTING ONE]
<a name="0158_0009">

\ prempt96.f ... a "medium-weight" pre-emptive multitasker for the
\ Vesta SBC196 running Forth-83i96.
\ Copyright *C* 1991 jack j. woehr
\ [email protected] JAX on GEnie
\ SYSOP RealTime Control & Forth Board (303) 278-0364 3/12/24 24 hrs.

\ *** Data Objects

\ Register Aliases

$ 00 constant zero  \ Symbolic Name for the Zero Register
$ 20 constant up    \ Forth83i96 User Pointer
$ 26 constant entry-reg \ Forth83i96 Multitasker assumes ENTRY in this reg.

\ Register Variables

$ 90 constant clicks    \ clicks left to execute on current task
$ 92 constant last-up   \ task that was executing last time interrupt fired
$ 94 constant period    \ how often the preemptive multitasker should fire
$ 96 constant reg-temp  \ a temporary register, used as a pointer and a holder
$ 98 constant save-stat \ PSW+INT_MASK+WSR+INT_MASK1 .. double length
$ 9C constant ax    \ Symbolic Name for a Scratch Register

\ Special Function Registers (SFRs) for Hardware Control
\ See _80C196KB USER'S GUIDE_, Intel 1990.

$ 08 constant int_mask  \ Int Mask containing Timer1 Int
$ 09 constant int_pend  \ Interrupt Pending register for Timer1 Overflow
$ 0A constant timer1    \ base address of Timer1
$ 14 constant wsr   \ Window Status Register, controls register windowing
$ 16 constant ioc1  \ Input/Output Control1 governs Overflow Int Enable

\ Bit Masks for Hardware Control

$ 04 constant enable    \ IOC1.2, Timer1 Overflow Interrupt Enable
$ 01 constant ov-int    \ INT_MASK.0 Timer1 Overflow

\ Interrupt Handle
\ Forth-83i96 ROM vectors ints thru regs

$ 48 constant timerov-handle    \ Vector for Interrupt 00

\ Declare a USER VARIABLE of which all tasks will possess an instance.
\ The local instance is the task's priority.

user variable my-pri
forth

\ Value 0 - 255 (since DJNZ instruction is used ... substitute DJNZW
\ on the 80C196KC part in the routine ?PREEMPT for greater range of
\ possible priority values).
\ 1 ... Task will execute one click maximum
\ 0 ... Task will execute 256 times

\ *** Preemptor Routines

\ Here is the return from pre-emption:

label reclaim   \ entry-reg == ENTRY
    up entry-reg ' ENTRY >body @ # sub3 \ load user pointer
    sp ' TOP >body @ up [+s] ld     \ get task's stack
    ' ENTRY >body @ up [+s] pop     \ get previous restart routine
    reg-temp $ 1A # ld          \ first register to restore
    ax $ 34 $ 1A - 2/ # ld          \ number of registers to restore
    dp@                 \ (resolve addr for DJNZ)
        reg-temp []+ pop        \ restore reg and postinc ptr
    ax djnz                 \ loop 'til done
    popa    \ restore flags from when task was interrupted preemptively
    ret c;  \ return address last thing waiting on stack

\ Here is the preemption:

label preempt   \ User Pointer still points to current task
        \ Ret addr & Proc Flags already on stack
    reg-temp $ 32 # ld      \ first register pair to preserve
    ax $ 34 $ 1A - 2/ # ld      \ number of register pairs to preserve
    dp@
        reg-temp [] push    \ save contents of a reg
        reg-temp 2 # sub2   \ "manual post decrement" mode!
    ax djnz
    ' ENTRY >body @ up [+s] push    \ save the restart routine
    sp ' TOP >body @ up [+s] st \ save address of TOP of stack
    reg-temp reclaim # ld       \ address of preempted task restarter
    reg-temp ' ENTRY >body @ up [+s] st \ install reclaim routine ...
    reg-temp ' LINK >body @ up [+s] ld  \ == ENTRY of next task
    ax reg-temp [] ld       \ @ENTRY == restart routine of next task
    ax reclaim # cmp        \ is new task's restart routine RECLAIM?
    0<> if          \ no, so we must restore intmask & psw "by hand"
        save-stat push
        save-stat 2+ push
        popa
    then
    entry-reg reg-temp ld
    ax br c;        \ start next task!

\ A label to DJNZ to while we wait for task slice to expire.

label one-more-goround popa ret c;

\ Timer Interrupt

label ?preempt
    pusha               \ save processor flags
    wsr $ 0f # ldb          \ switch Register Window to write timer
    timer1 period ld        \ set up next timer int
    wsr clrb            \ switch back
    up last-up cmp          \ executing same task as at last int?
    0<> if              \ no
        up last-up st       \ mark new task
        clicks ' my-pri >body @ up [+s] ld  \ get "priority"
        popa            \ restore processor flags
        ret         \ return from interrupt
    then                \ same task, decrement clicks
    one-more-goround clicks djnz    \ return if time not yet expired
    preempt sjmp c;         \ and if zero fall-thru, preempt

\ *** Hardware Setup

\ Install Interrupt Handler

: install-timer-int ( ---)  ?preempt timerov-handle ! ;


\ Control Counter and Interrupt Masks

code setup ( --)
    last-up clr         \ so that int will do setup first time
    pusha               \ save setup while changing wsr
    wsr $ 0F # ldb          \ change WSR to read IOC1, write timer
    timer1 period ld        \ write timer period to Timer1
    ax ioc1 ldb         \ get current IOC1 mask
    ax enable # orb         \ mask in Timer1 Overflow Int Enable
    popa                \ restore
    ioc1 ax ldb         \ store resultant mask to IOC1
    zero int_pend ldb       \ clear pending interrupts
    int_mask ov-int # orb       \ set Timer Overflow Int
    pusha               \ get int mask and psw
    save-stat 2+ sp [] ld       \ save "normal" processor status
    save-stat 2 sp [+s] ld      \ second word of same
    popa                \ restore
    tonext c;


\ Timer1 counts up and interrupts on overflow ( FFFF/0000 boundary)

: set-period ( CPU-state-times-desired/8 --) negate period ! ;

\ How many timer ints go by before a task is forcibly pre-empted?

: set-pri ( clicks-before-preemption task --) my-pri local ! ;

\ *** Sample Tasks That Don't Behave Themselves

\ A "naughty" task that doesn't PAUSE very often!

variable zotz
background: sample-task0 ( --)
    begin $ 1 zotz +! zotz @ 0= if pause then again ;

\ A "wicked" task that doesn't PAUSE at all!

variable foof
background: sample-task1 ( --) begin 1 foof +! again ;

\ Typical usage: HEX 100 100 TEST-SAMPLE-TASKS

: test-sample-tasks ( clicks period --)
    set-period
    dup
    foreground set-pri  \ set priority of foreground task
    dup         \ "naughty" task gets same CPU as FOREGROUND
    sample-task0 set-pri    \ set priority of "naughty" task
    4 /         \ we'll give "wicked" task less CPU
    sample-task1 set-pri    \ set priority of "wicked" task
    install-timer-int   \ install handler in vector handle
    setup           \ turn on interrupt
    sample-task0 wake   \ enable tasks to do other than "pass"
    sample-task1 wake
    ( multi)
    \ MULTI optional, since FOREGROUND task will be preempted anyway
    \ If MULTI not set, PAUSE won't work and "naughty" and "wicked"
    \ task become equivalent (except for priority).
;

\ Try this after you have executed TEST-SAMPLE-TASKS

: watch-sample-tasks ( --)
    zotz off foof off
    begin zotz @ u. foof @ u. key? until
    key drop ;

\ *** End of PREEMPT96.F


Copyright © 1991, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.