[comp.sys.handhelds] HP48 MPE V1.0 User's Guide

ervin@pinbot.enet.dec.com (Joseph James Ervin) (05/16/91)

                                      MPE


                A Multi-Programming Environment for the HP-48SX

                                       by

                                   Joe Ervin








     1  PURPOSE

     The purpose of this document is to describe the function  and  use  of
     the Multi-Programming Environment, written by Joe Ervin.



     2  INTRODUCTION

     The Multi-Programming Environment (MPE) is a set of  machine  language
     routines,   data  structures,  and  Star  macros  which  implement  an
     environment  whereby  the  software  developer  can   easily   program
     concurrent  tasks.  The immediate use of MPE is for graphics animation
     such as in games  programming,  where  multiple  objects  need  to  be
     animated on the screen at the same time (bullets, explosions, pac-men,
     etc.), however an environment such as this can be extremely useful for
     periodic  keyboard  polling  and  other events that need to occur from
     time to time.

     This distribution of MPE includes the STAR source code for MPE version
     1.0,  as  well as a set of process definitions which comprise a simple
     interactive graphics demo, showing the  main  features  of  MPE.   The
     source  code  and  the uuencoded file have been posted separately.  If
     you do not have access to either of these documents, please send  mail
     to "ervin@pinbot.enet.dec.com" and I will mail them to you.



     3  USER'S GUIDE

     The remainder of this document comprises the MPE User's  Guide.   This
     guide  discusses  the basic structure of a process under MPE, and then
     goes on to describe the various routines and macros and how  they  are
     used.   These  routines and macros allow the user to do such things as
     process scheduling, process entry-point redirection, and  interprocess
     communication.   Finally,  the example set of processes which comprise
     the demo are described at the end of the user's guide.

                                                                     Page 2


     3.1  Process Structure

     The basic idea behind MPE is that the programmer defines one  or  more
     "processes"  which are activated, or invoked by MPE.  MPE keeps a list
     of all processes which are waiting to run, and runs the  processes  as
     they  come  due.   As  each  process  is  run,  it is deleted from the
     run-list, thus  requiring  processes  to  be  rescheduled  after  each
     invocation if repeated execution is desired.

     The manner in which MPE handles processes is as follows.  The run-list
     contains  a  set  of process ID/time pairs, where the "ID" is an 8-bit
     process ID, and the "time" is the absolute time (in  clock  ticks)  at
     which  that  process  should be run.  The clock ticks referred to here
     are the same "ticks" as those  that  are  associated  with  the  TICKS
     command  described  in  the  HP48 owners manual.  There are 8192 clock
     ticks  per  second.   The  RUN_LIST  is  a  data  structure  used  and
     maintained  by  MPE.  The casual programmer need not be concerned with
     its workings.

     The association between processes and IDs is implied through  the  use
     of global labels in the assembler code for the processes.  MPE expects
     that each process will have at its beginning a label of the form:

     PROCESSx_INIT:

     ...where the x in PROCESSx_INIT is a decimal number from 1 to 255.   A
     value of 0 may NOT be used as a process ID since the MPE reserves this
     ID for the scheduler.

     The  portion  of  MPE  that  manages  the  run-list  is   called   the
     "scheduler", and will be referred to as such for the remainder of this
     document.  When an application written using MPE is first started, the
     scheduler's  run-list is empty.  In order to activate the application,
     MPE automatically schedules process #1 for immediate execution.  As  a
     result,  the control flow will pass immediately to the code located at
     PROCESS1_INIT:.

                                      NOTE

             This is the only time that a process will  be  invoked
             automatically by the MPE.  It is the responsibility of
             the programmer to  ensure  that  all  processes  which
             comprise   the   application   are  scheduled  by  the
             application itself.  Processes are scheduled by  using
             the ADD_PROCESS routine, described below.


     Initially, MPE defines the entry points of all  processes  to  be  the
     PROCESSx_INIT  label  of each process.  In many applications, however,
     it will be necessary for a process to execute its initialization  code
     only  once;  the  first  time  the  process is activated.  In order to
     facilitate the distinction between process initialization code and the
     main  process  body,  MPE includes a macro called PROCESS_START, which
     allows the process to redefine its entry point.

                                                                     Page 3


     The PROCESS_START macro takes two parameters; the ID  of  the  process
     whose  entry  point  is to be modified, and the label corresponding to
     the desired entry point.  For example, Figure 1 shows process #1 using
     the  PROCESS_START  macro to alter its entry point.  This example also
     shows the use of several of the other facilities in MPE.  These  other
     facilities will be described in later sections.

     ;**********************************************************************
     PROCESS1_INIT:          ; The initial entry point for process #1.

             PROCESS_START 1, PROCESS1_CODE  
                             ; Changes the entry point of process #1 to the 
                             ; PROCESS1_CODE label for subsequent 
                             ; invocations.
             . 
             (Initialization code for process #1.)
             . 
     PROCESS1_CODE:          ; The main body of process #1 starts here.  
             . 
             (Body of process #1)
             .
             SAVE_CONTEXT    
             .       ; This causes MPE to save a "snapshot" of this 
             .       ; process's register context (B, D, D0, D1, R0-R4) 
             .       ; so that the next time this process is started, its 
             .       ; registers will contain the same data as at the 
             .       ; time SAVE_CONTEXT is called.  A and C are not saved.
             .
     RESCHEDULE:     ; Now we want to schedule this process to execute again
                     ; in 1 second.   

             ADDR    CUR_TIME, D0    
                     ; ADDR is a standard HP48 macro found in the HP48.STAR 
                     ; macro library by Jan Brittenson. CUR_TIME is a global 
                     ; MPE variable which holds a copy of the time 
                     ; (in ticks) when this process was activated. 
             MOVE.W  @D0, C
             CLR.W   A
             MOVE.P5 ^x1FFF, A
             ADD.W   A, C    ; 

             ; CUR_TIME + ^x1FFF ticks = CUR_TIME + 1 sec.

             MOVE.P5 ^x1, A  ; We are rescheduling process #1. This would 
                             ; be ^x2 for process #2, ^x3 for process #3, 
                             ; etc..
             CALL    ADD_PROCESS     ; Schedule process #1 for execution 
                                     ; at 1 sec. from when the current 
                                     ; invocation started.
             JUMP    TO_SCHEDULER    ; Jump back to the scheduler.
     ;**********************************************************************


                                    Figure 1

                                                                     Page 4


     In addition to the PROCESS_START macro, there is the CUR_PROCESS_START
     macro,  which  is  very  similar  to  PROCESS_START,  but  with  small
     differences.  The CUR_PROCESS_START macro works only  on  the  current
     process.   The  CUR_PROCESS_START  macro  expects  that  the  user has
     previously loaded the address corresponding to the new entry point for
     the  current  process  into C.A.  Invoking the CUR_PROCESS_START macro
     will then take that address in C.A and cause the  current  process  to
     use  that  as  its  new entry point.  The ADDR macro in the HP48 macro
     library by Jan Brittenson is useful for loading C.A with  the  address
     of the new entry point.

     The main impetus for the CUR_PROCESS_START macro is to avoid the  need
     of multiple IF_PROC/ENDIF_PROC constructs for code which may be shared
     by many processes.  In games that require many objects on the  screen,
     each  of which with its own process, it is conceivable that the number
     of IF_PROC/ENDIF_PROC constructs required by the  PROCESS_START  macro
     would be unwieldy.



     3.2  Process Context

     3.2.1  The SAVE_CONTEXT Routine

     One of the main functions of MPE is to ensure that  each  process  has
     its  own processor context.  To do this, MPE provides the SAVE_CONTEXT
     routine which automatically  saves  the  processor  registers  of  the
     current process in a special data structure maintained by MPE for each
     process.  The registers saved are B, D, D0, D1, and R0-R4.  Note  that
     MPE makes no attempt to save Registers A and C between invocations.

     By calling the SAVE_CONTEXT routine, a process indicates to  MPE  that
     the  current  contents of its registers should be used as the starting
     contents when the process is next invoked.  It is  the  responsibility
     of  the programmer to call SAVE_CONTEXT at an appropriate point in the
     process when all the registers have the desired  contents.   MPE  does
     not  provide a mechanism for saving or restoring individual registers.
     The SAVE_CONTEXT  routine  alters  A  and  C,  but  leaves  all  other
     processor registers intact.

     MPE also provides the SAVE_CONTEXT_BY_A routine which allows the  user
     to  specify  the register context for any arbitrary process.  The user
     provides a process ID in  register  A.B,  and  SAVE_CONTEXT_BY_A  will
     write  the  current  register  context  into  the context storage area
     maintained by MPE for that process.  Note that no  error  checking  is
     done  on  the  ID  provided in A.B, so the user should be careful with
     using this routine.



     3.2.2  The RESTORE_CONTEXT Routine.

     Whenever a process comes due and is about to be executed, MPE uses the
     RESTORE_CONTEXT  routine  to  restore  the process's register context,
     after  which  MPE  jumps  to   the   process's   entry   point.    The

                                                                     Page 5


     RESTORE_CONTEXT  routine,  together  with the SAVE_CONTEXT routine can
     also be useful to the  programmer  in  general.   The  RESTORE_CONTEXT
     routine restores the register context of the process whose ID is given
     in A.B.  Obviously, this routine effects all the processor registers.

     The RESTORE_CONTEXT routine can be useful to the  programmer  whenever
     information  needs  to  be  shared between processes.  For example, in
     some applications, it may be desirable  to  extract  information  that
     another  process  is  keeping in one of its processor registers.  This
     may be accomplished by using the RESTORE_CONTEXT  routine,  specifying
     the other process ID in A.B.  Note that it is probably advisable for a
     process to save  its  own  context  via  SAVE_CONTEXT  before  calling
     RESTORE_CONTEXT to see the other process's registers.  That way, after
     the extracted data has been used, the  process  can  restore  its  own
     context if necessary and continue execution.

     Note that although the  SAVE_CONTEXT  routine  does  not  require  any
     arguments, the RESTORE_CONTEXT routine requires the process ID in A.B.



     3.2.3  The CURRENT_CONTEXT_ID Variable

     In order to keep track of where to  save  register  context  when  the
     SAVE_CONTEXT  routine  is  called,  MPE  maintains  a  variable called
     CURRENT_CONTEXT_ID.  This is a one byte variable which contains a copy
     of the current process's ID.  While this variable is primarily for use
     by the SAVE_CONTEXT routine,  it  comes  in  very  handy  for  certain
     applications.

     One useful action that involves  the  CURRENT_CONTEXT_ID  is  that  of
     modifying  the  saved  state  of another process.  This can be done by
     first saving the current process's context, then restoring  the  other
     process's  context.   At this point, the current process has access to
     all the register context of the other  process.   By  modifying  these
     contents,  changing  the  CURRENT_CONTEXT_ID  variable  to  the  other
     process's ID, and then executing the SAVE_CONTEXT routine,  the  other
     process's  context  can  be  overwritten.  The programmer must be very
     careful to ensure that the CURRENT_CONTEXT_ID variable is returned  to
     the  current  process's  ID  before  control  is  passed  back  to the
     scheduler.

     Another useful technique involving the CURRENT_CONTEXT_ID variable  is
     that  of  sharing  code among several processes.  This is discussed in
     detail below.

     To make accessing the CURRENT_CONTEXT_ID variable easier, MPE includes
     a routine called GET_CURRENT_ID, which when called returns the current
     process ID into A.B.  This routine modifies the contents of A.

                                                                     Page 6


     3.3  Scheduling Processes - The ADD_PROCESS Routine

     As stated above, it is necessary for the user's  code  to  handle  the
     scheduling  of  all  processes,  except  for  the initial execution of
     process #1  at  startup.   Processes  are  scheduled  by  use  of  the
     ADD_PROCESS  routine.   This routine takes as its arguments a one byte
     process ID in register A, and a 16-nibble  absolute  time  (ticks)  in
     register  C.   ADD_PROCESS  then  schedules  the specified process for
     execution at the specified time.

     The ADD_PROCESS routine modifies A, C, B, D, D0, D1, and R0, so it  is
     recommended   that   this   routine   be   executed  only  during  the
     initialization phase of a process, after the  process  has  saved  its
     register  context via the SAVE_CONTEXT routine, or any other time when
     the contents of these registers is not critical to the process.



     3.4  The CUR_TIME Variable

     In order to reschedule a process at a certain approximate interval  it
     is necessary that the process have knowledge of the system time at the
     point when the process was invoked.  MPE provides this information  in
     the  CUR_TIME variable.  The CUR_TIME variable holds a 16 nibble value
     representing the system time when the current process was invoked.  To
     schedule  itself  on regular intervals, a process should read the time
     value at CUR_TIME, add in a time  delay,  and  reschedule  itself  for
     execution  at the resulting future time.  Figure 2 shows a typical use
     of CUR_TIME.  In this example, process #3 is rescheduling  itself  for
     CUR_TIME plus 1 second.  This causes process #3 to execute on 1 second
     intervals.

     ;********************************************************************
     ; Reschedule process #3 for execution in 1 second.

             ADDR    CUR_TIME, D0            ; Point D0 to the CUR_TIME
                                             ; variable.
             MOVE.W  @D0, C                  ; C.W now contains the time 
                                             ; when this process was
                                             ; invoked by the scheduler.
             MOVE.P5 ^x1FFF, A       ; ^x1FFF = ^d8192 = 1 second.
             ADD.W   A, C
             MOVE.P2 ^d3
             CALL    ADD_PROCESS     ; Add the process to the RUN_LIST.
             JUMP    TO_SCHEDULER    ; Return control to the scheduler.

     ;********************************************************************


                                    Figure 2


     The CUR_TIME variable can be accessed by using the ADDR macro from the
     HP48 macro library by Jan Brittenson.  This method also works well for
     accessing any other MPE variables.  The CUR_TIME variable can also  be

                                                                     Page 7


     accessed  by  calling  the  GET_CUR_TIME  routine,  which  returns the
     CUR_TIME variable in C.W.  This routine modifies register A.



     3.5  Returning Control To The Scheduler

     After a  process  has  completed  its  useful  work  for  the  current
     invocation, it needs to save its register context via the SAVE_CONTEXT
     routine, and then reschedule itself if  desired.   Finally,  the  last
     thing  a process does is return control to the scheduler by jumping to
     the MPE label TO_SCHEDULER as follows:

             JUMP    TO_SCHEDULER    ; Returns control to the scheduler.


     The user should be careful to use the JUMP instruction  here  and  NOT
     the CALL instruction.



     3.6  Sharing Process Code

     In many  applications,  several  processes  may  be  needed  which  do
     essentially   the   same  thing.   Consider  the  situation  where  an
     application wants several processes which do very similar things, such
     as  "bullets"  flying about on the screen as is common in many popular
     arcade  games.   Since  each  of  these  processes  would  be  running
     essentially  identical  code,  it  would be very useful if each of the
     "bullet" processes could actually  share  the  same  code.   This  can
     easily  be  accomplished  by placing multiple PROCESSxINIT:  labels at
     the top of the process.

     One problem that soon becomes apparent, however, is that  this  shared
     code  must  inevitably  perform some actions that are specific to each
     process.  It is therefore necessary that the shared code has a way  of
     determining which process is currently executing.  One example of this
     is that since these processes will need to reschedule themselves,  the
     shared  code  must  have some way of rescheduling the correct process.
     By accessing the CURRENT_CONTEXT_ID, the shared code can determine the
     ID of the process it is currently servicing.

     The CURRENT_CONTEXT_ID variable can be  accessed  by  using  the  ADDR
     macro  from  the  HP48  macro  library  by Jan Brittenson, as is shown
     below.  This method also  works  well  for  accessing  any  other  MPE
     variables.

     ;********************************************************************
     ; Check which process is currently running.

             ADDR    CURRENT_CONTEXT_ID, D0  ; Point D0 to the 
                                             ; CURRENT_CONTEXT_ID variable.
             MOVE.B  @D0, C                  ; C.B now contains the ID of the
                                             ; current process.


                                                                     Page 8


     ;********************************************************************


                                    Figure 3


     Because of the usefulness of code-sharing, MPE contains a macro  which
     makes  the  conditional execution of code based on the current process
     ID very simple.

     The IF_PROC and ENDIF_PROC macros allow the programmer to easily cause
     the execution of a specific section of code conditioned on the current
     context ID.  The format of this structure is as follows:
     ;********************************************************************

             IF_PROC n       ; where "n" is the process ID in question.
               .
               .
               (ML code to be executed only if process #n is current.)
               . 
               . 
             ENDIF_PROC
     ;********************************************************************


                                    Figure 4



                                      NOTE

             The user  should  be  aware  that  the  IF_PROC  macro
             modifies registers A and C regardless of which process
             ID is current.  For this  reason,  A  and  C  will  be
             altered  whenever  IF_PROC  is  used.  The user should
             therefore make no  attempt  to  pass  values  into  an
             IF_PROC/ENDIF_PROC construct using registers A or C.

     As an example of when to use IF_PROC, consider a section of code which
     is  shared by processes #1, #2 and #3.  Let us assume that the process
     needs to reschedule itself after executing.  It is therefore necessary
     that  the  code  pass  the  proper ID in register A to the ADD_PROCESS
     routine, regardless of which process (#1, #2, or #3) is executing  the
     code.   The  following  example  represents one way that this could be
     accomplished.



                                                                     Page 9


     ; *****************************************************************
     ; Example of how to use IF_PROC.
     ; This code assumes that the next desired run-time for this process
     ; has been loaded into R0.


             IF_PROC 3
               MOVE.W        R0, C   ; Next time process should be run.
               MOVE.P2       ^d3, A  ; ID for this process into A.B.
               JUMP  PROCESS_SCHED           
             ENDIF_PROC

             IF_PROC 4
               MOVE.W        R0, C   ; Next time process should be run.
               MOVE.P2       ^d4, A  ; ID for this process into A.B.
               JUMP  PROCESS_SCHED
             ENDIF_PROC

             IF_PROC 5
               MOVE.W        R0, C   ; Next time process should be run.
               MOVE.P2       ^d5, A  ; ID for this process into A.B.
               JUMP  PROCESS_SCHED
             ENDIF_PROC

     PROCESS_SCHED:  CALL    ADD_PROCESS     
                                     ; Schedule the current process to 
                                     ; run at the time given in C.   
             JUMP    TO_SCHEDULER    ; Return control to the scheduler.
     ; *****************************************************************


                                    Figure 5



     3.7  Interprocess Communication

     In previous sections it was described how  interprocess  communication
     could  be  done by modifying the latent process's context.  While this
     is valid, it is a terribly inefficient means for communicating between
     processes.   A  much  better  way is to use global variables which are
     shared between processes.  Since processes are  never  interrupted  by
     the  scheduler,  there  is no need to synchronize access of the shared
     variables, so processes may pass information  back  and  forth  freely
     using this scheme.



     3.8  Process Timing

     One issue that the programmer needs to be aware of when running  under
     MPE  is that of process latency, particularly under heavy CPU loading,
     and the effect that this latency has on process timing.

     Consider a scenario  where  many  processes  are  running  under  MPE.

                                                                    Page 10


     Looking  at two of these processes, say #1 and #2, let us pretend that
     process #1 always  schedules  itself  to  run  at  CUR_TIME  +  1/32th
     seconds, while process #2 always schedules itself to run at CUR_TIME +
     1/16 seconds.  One  would  therefore  expect  that  process  #1  would
     execute  32  times  each  second, and that process #2 would execute 16
     times each second.  If  the  CPU  is  lightly  loaded,  then  this  is
     (almost)  true.   If,  however, the CPU is heavily loaded by the other
     processes running under MPE, then processes #1 and #2 will undoubtedly
     run  less  frequently  than their reschedule intervals would indicate.
     Furthermore, both processes will execute the same number of  times  in
     any  given  time  interval.  The exact frequency of repetition will be
     determined by the degree of CPU loading and the  number  of  processes
     running under MPE.

     The reason for this is as follows.  When the CPU  is  heavily  loaded,
     processes  which schedule themselves a very short time into the future
     will always be  overdue  when  the  scheduler  checks  them.   In  our
     scenario,  this  means that processes #1 and #2 will always be overdue
     by the time the scheduler gets a chance to check them in the run-list.
     The result is that each process will be run exactly once for each pass
     the scheduler makes through the run-list.

     Another way to understand the problem is to consider a simple graphics
     process  whose  job is to move a "bullet" across the screen at a given
     rate.  One way to do this would be to have the process move the bullet
     a  pixel  or  so  and then reschedule itself for a short time into the
     future.  The time for which the process reschedules  itself  could  be
     given as the time the process was most recently executed (as stored in
     CUR_TIME)  plus  some  incremental  time  chosen  to  give  a  desired
     repetition rate for the process.  While this simple approach will work
     well when the CPU is lightly loaded, it does not work as well when the
     CPU is very heavily loaded.

     What happens when the CPU is heavily loaded is that processes may  sit
     in  the  scheduler's  run-list  long past the time for which they were
     scheduled, simply because the CPU  is  very  busy  running  the  other
     processes  in  the  system.   Considering  the  "bullet" process, this
     results in "lost" time, and causes the bullet on the  screen  to  slow
     down.  The reason time is "lost" from the perspective of the bullet is
     that being ignorant of absolute time (the time indicated by the system
     clock)  the  process  always  reschedules  itself relative to when its
     current invocation started, as indicated  by  CUR_TIME.   The  process
     makes  no attempt to "catch-up" to where the bullet would have been if
     the CPU had been lightly loaded.

     One solution to this problem is to schedule processes based not on the
     time  value  stored  in  the  CUR_TIME  variable,  but rather based on
     current time value in the system clock.  Doing this enables a  process
     to  recover time that was lost to other processes.  In the case of our
     "bullet" process, the process code could  examine  the  system  clock,
     determine  how  much  real  time has passed since the process was last
     invoked, and then move the bullet a number of pixels  consistent  with
     the  desired  rate of travel.  The visual result on the screen in this
     case is that the bullet has the desired average rate of travel  across
     the  screen,  regardless of CPU loading.  As a side effect, the motion

                                                                    Page 11


     of the bullet may appear a little "jittery" since the process may move
     it  several  pixels in rapid succession during each invocation, rather
     than  moving  it  one  pixel  per  invocation  as   in   the   simpler
     implementation.

     The programmer should  be  very  careful,  however,  when  programming
     processes  which  attempt  to  "catch  up" to the system clock in this
     manner.  To see how this  type  of  process  definition  can  lead  to
     problems,  consider  a heavily loaded system with two bullet processes
     which run based on the absolute system time as described above.

     To facilitate the reading of the system clock, MPE includes a  routine
     called  GET_TICKS  which  returns the current (16 nibble) value of the
     system clock to register C.  This routine  modifies  register  A,  but
     leaves all other processor registers intact.

     Examples of both implementations of the "bullet" process can  be  seen
     in  the  demo processes included with the MPE distribution.  This demo
     also illustrates the problems regarding the  relative  frequencies  of
     process invocation in a heavily loaded system.



     3.9  Assembly-time Errors

     Because of the way MPE sets up its data structures  for  managing  the
     processes,  it  is  necessary  that  the  processes  be defined by the
     programmer  with  contiguous  process  IDs.   For  example,   if   the
     programmer has defined process #4, then processes #1-#3 must also have
     been defined.  If the programmer leaves any  "holes"  in  the  process
     numbering scheme, MPE will generate an error message to that effect at
     assembly-time.



     3.10  Run-time Errors

     There are two common errors that the programmer  might  be  likely  to
     make when using MPE.  Both involve the use of the ADD_PROCESS routine.
     The first  scenario  is  when  the  user  attempts  to  schedule  more
     processes  than  there  are process slots in the scheduler's run-list.
     The current implementation of STAR allows only 10 pending processes in
     the run-list at a time.  If the user attempts to schedule more than 10
     processes concurrently, MPE will push onto the stack an error code  of
     99d,  indicating  that  the  run-list  overflowed,  and  the ID of the
     process which made the call to ADD_PROCESS when the failure  occurred.
     Both  of  these  values are pushed as short binary integers.  MPE then
     halts execution of the machine language code and  continues  with  the
     RPL thread.

     The size of the  run_list  can  be  easily  increased  to  accommodate
     applications which need more than 10 processes scheduled concurrently.
     Refer to the section below on "MPE Customization".

     The other potential misuse of  ADD_PROCESS  is  when  the  user  calls

                                                                    Page 12


     ADD_PROCESS  with  an  invalid  process  ID.   I  did  this once while
     debugging MPE and my machine  promptly  crashed  with  "memory  lost".
     Very unforgiving.

     Therefore, MPE now checks the ID passed into ADD_PROCESS to make  sure
     it  corresponds  to one of the processes defined by the user.  If not,
     then MPE pushes an error code of 98d onto the stack as a short binary,
     indicating  that  an invalid ID was passed into ADD_PROCESS.  MPE then
     also pushes as short binaries the  ID  of  the  process  which  called
     ADD_PROCESS,  and  the  erroneous  ID,  in that order.  MPE then halts
     execution of the machine language code  and  continues  with  the  RPL
     thread.



     3.11  MPE Customization

     The current  implementation  of  MPE  will  allow  up  to  20  defined
     processes.   The user should note, however, that the version of MPE in
     this distribution will only allow  as  many  as  10  processes  to  be
     concurrently  scheduled.   This  has been done to optimize performance
     for the set  of  process  definitions  which  comprise  the  MPE  demo
     application.   This can easily be changed by the programmer to hold as
     many processes as you need.  Read on.

     The scheduler scans through  the  run  list  in  a  circular  fashion,
     updating  its  copy  of  the  system  time  once  per pass through the
     run-list.  As the scheduler does  a  pass  through  the  run-list,  it
     checks  the  time stamp of any valid processes against its copy of the
     system time and runs the associated  process  if  its  time  stamp  is
     earlier  than  the current system time.  Because of this circular scan
     algorithm, it is highly advisable to limit the size of the run-list to
     the   number  of  processes  which  the  programmer  expects  to  have
     concurrently scheduled.  In this way, the  scheduler  does  not  waste
     valuable CPU time checking run-list slots which will never be filled.

     The run-list is a static data structure  located  at  the  STAR  label
     "RUN_LIST:".   The  programmer should increase or decrease the size of
     this data allocation as needed, being sure to update the RUN_LIST_SIZE
     variable at the same time.  (RUN_LIST_SIZE is a STAR variable which is
     defined just above where the RUN_LIST is located in the MPE  sources.)
     For each new slot in the RUN_LIST, the programmer should add a "DATA.B
     0" to hold the process ID and then a "DATA.W 0" to  hold  the  process
     time  stamp.  The RUN_LIST_SIZE variable indicates the number of slots
     in RUN_LIST.

     If the current maximum of 20 processes is  insufficient  for  a  given
     application,  then  the programmer should add lines of code to the MPE
     data structure and initialization sections which appears  just  before
     and  after  the  START:  label.  The lines which will need to be added
     should be fairly obvious, and will have the form of:

                                                                    Page 13



     (appearing just before the START: label)

             GEN_HEADERS 21
             GEN_HEADERS 22
             .
             .
             .

             GEN_DESCRIPTOR 21
             GEN_DESCRIPTOR 22
             .
             .
             .

     (...and then appearing just after the START: label)

             HEADER_INIT 21 
             HEADER_INIT 22
             .
             .
             .
      



                                    Figure 6


     The new lines must be added to the end of each section (after the line
     corresponding  to  process  #20).   You  will  need  to  make  similar
     additions in the FILL_DESCRIPTORS macro, adding more "FILL  xx"  lines
     for  the  additional  processes.  The need to make the above additions
     will be removed in a later version of MPE.

     If anyone needs more than 20 processes, please send mail.  I  want  to
     see your application :-)



     3.12  Assembler Considerations

     Due  to  limitations  in  the  current  version  of  Star   (V1.04.4),
     applications   running   under   MPE  should  be  assembled  with  the
     jumpification feature turned off.  This is done by using the -j switch
     on  the  command  line  when  STAR  is  run.   For help on the various
     switches which can be used on the STAR command line, invoke STAR  with
     the  -h  switch,  which will cause STAR to print out its standard help
     text.

                                                                    Page 14


     4  MPE DEMO PROCESSES

     Included with this distribution of MPE  is  a  set  of  seven  process
     definitions   which  demonstrate  the  basic  features  of  MPE.   The
     following sections will describe briefly what each of these  processes
     do and how they interact.



     4.1  Application Overview

     This demo application is a simple demonstration of how MPE can be used
     to  do  graphical  animation of several objects on the screen at once.
     This also provides a good visual  demonstration  of  the  CPU  loading
     effects described in earlier sections.

     Basically,  the  application  consists  of  a  fire  button  and  four
     "bullets".   This  application uses the [+] button as the fire button.
     When the user starts the application, the screen blanks and waits  for
     the  user to press the fire button.  For each press of the [+] button,
     the application will fire a bullet.  The bullets start  at  the  upper
     left  of  the  screen and move down the screen, row by row, until they
     reach the bottom of the screen.  When any  bullet  reaches  the  lower
     right  of  the screen, the application stops.  When the user has fired
     all four bullets, the fire button is disabled for the remainder of the
     application.   The  [.]  key  quits  the  application.   Refer  to the
     following section for how to download and run the application.



     4.2  Downloading And Running The Application

     Included in this distribution is  the  uuencoded  binary  file.   This
     binary  file is a directory containing the machine language code which
     makes up the MPE application, and a short RPL program which  runs  it.
     The  user  should  uudecode  this file and then download the resulting
     binary file in the usual manner to the HP48SX (make sure  to  set  the
     HP48SX  kermit  to  binary  mode.  The RPL program, "DEMO", in the new
     directory serves only to clear the display and set the display to PICT
     memory  before  running the machine language application ("XXX").  You
     should _not_ run the "XXX" program directly, since if PICT memory  has
     been  purged (by doing a PICT PURGE), then the application may corrupt
     memory.  Once started by  pressing  the  button  labeled  "DEMO",  the
     machine  language  program  can be aborted at any time by pressing the
     [.] button.



     4.3  Process Descriptions

     The following sections describe each of the seven processes which make
     up the application.  Note that processes #1 and #2 are not integral to
     the application and could be omitted.

                                                                    Page 15


     4.3.1  Process #1

     This is the process which is auto-executed  by  MPE  at  startup.   In
     terms  of the application, the only thing that this process does is to
     start up the keyboard scanner process  (#7).   Another  function  that
     this  process  performs,  in  conjunction  with  process #2 is to do a
     register context integrity test periodically while the application  is
     running.  This was written mainly for debugging MPE, but may be useful
     to the programmer in general, so I have left it in.  It runs once  per
     second, so it does not add significant overhead to the application.



     4.3.2  Process #2

     This process does nothing except clear all  the  processor  registers.
     The  purpose of this process is to work with process #1 to implement a
     register context integrity test.



     4.3.3  Processes #3, #4, And #5.

     These processes are grouped together here because they share the  same
     process  definition.   This process definition shows a good example of
     how to share code between two or more processes.  The  code  for  this
     process  definition  makes  use of the IF_PROC/ENDIF_PROC macros to do
     conditional execution of certain sections of code based on the current
     context ID.  For example, the three bullets owned by processes #3, #4,
     and #5 all move at different speeds on the screen.  This was done here
     to  visually  show the independence of the bullet processes running in
     the system.  The process definition uses the IF_PROC/ENDIF_PROC macros
     to  schedule  the  correct  process  at  the  correct  interval,  thus
     determining the speed of each bullet.

     One thing to note is that this process definition does not attempt  to
     control the speed of a bullet with great accuracy.  In other words, as
     the four bullets are fired and the CPU becomes  more  heavily  loaded,
     the  speed  of  the  bullets corresponding to processes #3, #4, and #5
     will slow down slightly.  This  is  due  to  process  latency  in  the
     scheduler.   Process  #6 shows a similar bullet process, but one which
     keeps track of absolute time and maintains an accurate velocity on the
     screen, independent of CPU loading.



     4.3.4  Process #6

     This  process  definition  is  very  similar  to  that  which  defines
     processes #3, #4,and #5.  The main difference is that process #6 keeps
     track of absolute time, making sure that its bullet moves  at  exactly
     the  velocity  indicated in the process definition.  In this way, even
     though the bullet for this process does not start  until  the  CPU  is
     already  very  busy with the other three bullets, it will maintain its
     velocity at the expense of the other three bullets.

                                                                    Page 16


     The effects of the use  of  absolute  time  in  this  process,  versus
     relative  time in the other three bullet processes, can be seen in two
     ways.  The first way is that after firing the four bullets, it  should
     be  discernible  that  the  fourth  bullet (process #6) moves slightly
     faster than the second one (process #4).  By examining the definitions
     of these processes, one quickly sees that processes #4 and #6 have the
     same rescheduling interval.  The  difference  in  speed  is  therefore
     attributable  to  the  time that process #4 loses while sitting in the
     RUN_LIST waiting to be run by the scheduler.  Since process  #6  keeps
     track  of  absolute  time  internal  to  itself,  this queuing time is
     accurately recovered each time the process is invoked.

     The second way that the use of absolute time in process #6 can be seen
     is  by  pressing  and briefly holding the [ON] key.  Pressing this key
     temporarily halts execution of the calculator, causing  all  processes
     to pause.  When the key is released, however, the bullet of process #6
     will quickly "zip" ahead to catch up to where it should be.  A similar
     effect  can  also  be  seen  by  pressing  and  holding some other key
     (besides  [ON]).   Since  the  keyboard  controller  causes  lots   of
     interrupts  in when this is done, it has the effect of heavily loading
     the CPU.  What can be seen on the display is that while the user holds
     down  a  key and thereby loads the CPU, the three bullets which do not
     use absolute timing slow down noticably, while the bullet for  process
     #6  maintains  its  correct  average  velocity.   The  manner in which
     process #6 "catches up" to where it should  be  can  be  seen  in  the
     bullet's jumpiness.



     4.3.5  Process #7

     This process comprises the  keyboard  scanner.   This  process  simply
     checks the keybuffer for the [+] key, and if it finds it then the next
     bullet process is scheduled for execution.  If the [.] key is pressed,
     this  process will cause the application to exit and continue with the
     RPL thread.  This process runs approximately 8 times per second.