[comp.sys.handhelds] HP48 MPE + DEMO Star source

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

; MPE V1.0.  Copyright Joseph James Ervin 1991.

; The following is a simple multiprogramming environment (MPE) which allows
; users to program concurrent processes to run on the HP48SX.  

; Addresses of Rom routines

        save_registers  =       ^x679B  ; Saves system registers. Uses C, D0.
        restore_registers =     ^x67d2  ; Restores system register.
        rplcont         =       ^x71BE  ; Jumps to the next RPL instruction.
        rr_rplcont      =       ^x5143  ; Restores the system registers and
                                        ; then does rplcont.
        get_short       =       ^x6641  ; Pops the short of TOS and returns it
                                        ; into A.A
        push_a_cont     =       ^x357C  ; Push the contents of A.A onto stack
                                        ; and continue RPL.
        push_r0_short   =       ^x6537  ; Push R0 as new system binary.  This
                                        ; routine uses the SAVED system
	ROM_GET_TICKS	= 	^x130E	; Returns the number of timer ticks
					; in C.13.  Trashes just about every
						; other register.
    	radix ^d10
	
; General address definitions
	bos		= 	^x7057E	; Pointer to beginning of the stack.
	pict_pointer	=	^x70565	; Pointer to PICT grob.
        menu_pointer	= 	^x70556	; Pointer to Stack Grob.
	keybuf		= 	^x704ea ; keyboard buffer.

; ***************************************************************************
; The following macro is used to generate a process header and to allocate 
; storage for the process context.  The macro  requires as its only parameter 
; the ID of the process for which the process header is to be generated. 

	macro   gen_headers n=0, label
	save n
        n = $n
        if (n==0)   ; This is for the scheduler.
$(label)_context:
          DATA.W  0, 0            ; Storage for B, D.
          DATA.A  0, 0            ; Storage for D0, D1.
          DATA.W  0, 0, 0, 0, 0   ; Storage for R0, R1, R2, R3, R4

$(label)_header:     data.a 0, 0

	else  ; If not for scheduler.
        if def process$(n)_init

; The user got it right and defined the _init section of the
; process.  Now we need to allocate storage for the context area and build the
; process header.

$(label)_context:
        DATA.W  0, 0            ; Storage for B, D.
        DATA.A  0, 0            ; Storage for D0, D1.
        DATA.W  0, 0, 0, 0, 0   ; Storage for R0, R1, R2, R3, R4

$(label)_header:	data.a 0, 0
	endif
        endif
	restore n
	endmacro
	hide gen_headers                 
; ***************************************************************************
; The following macro is used to generate the process descriptor list used
; by the scheduler.  This macro checks to see that the processes declared by
; the user have contiguous process IDs.  If not, then an error message is 
; generated.

	macro gen_descriptor n
	save n
        n = $n
	if (n == 0)
DESCRIPTOR_LIST: DATA.A	scheduler_header	; Process descriptors.
	  contig = 1
	  num_of_proc = 0
	endif
	if (!def process$(n)_init && !(n==0))
	  contig = 0
	endif
	if def process$(n)_init
	  num_of_proc = (num_of_proc + 1)
	  if (contig==0)
	    error Defined processes are not contiguously numbered.
	  endif
	

; The user got it right and defined both the _init and _code sections of the
; process.  The process context storage and header should already have been 
; set up, so now we need to add the entry for the process descriptor.

	  DATA.A	process$(n)_header	; Process descriptor.
	endif

	restore n
	endmacro
	hide gen_descriptor                 




; ***************************************************************************
; The following macro is used to make the relative addresses stored into the
; process header into absolute addresses.  


	macro header_init n
	save n, dest, src1, src2
        n = $n
	if ((def process$(n)_init) || (n==0))

; The user has defined this process, so we need to write the address pointers
; in the process header. 

	  if (n==0)
	    dest = scheduler_header
	    src1 = scheduler_context
	    src2 = scheduler_code
	  else
	    dest = process$(n)_header
	    src1 = process$(n)_context
	    src2 = process$(n)_init
	  endif

	  addr	$dest, D0	; Point D0 to the process (scheduler) header.
	  addr	$src1, c	; Get address of context.
	  move.a	c, @d0
	  addr	$src2, c
	  add.a	^d5, d0
	  move.a	c, @d0

	
	endif	
        restore n, dest, src1, src2
	endmacro
	hide header_init

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

	macro	fill	n
	save n
	n = $n

	if def process$(n)_init
	addr	process$(n)_header, c
	move.a	c, @d0	; Fill in the process descriptor.
	add.a	5, d0	; D0 now points to the next process descriptor.
	endif


	restore	n
	endmacro
	hide fill	

; The following macro writes the address pointers in the process descriptor
; list.

	macro descriptor_init

	; We know that the scheduler process exists, so we can fill in that
	; descriptor.
	
	addr	descriptor_list, d0	; First, point d0 at the descriptors.
	addr	scheduler_header, c	; Get the scheduler descriptor.
	move.a	c, @d0			; Fill in scheduler descriptor.
	add.a	5, d0	; D0 now points to the next process descriptor.

; Now we want to sequence through the processes.  For each process, if the 
; user has defined the process, then we need to fill in the descriptor.

	fill 1 	; fill in descriptors for the 20 processes.
	fill 2
	fill 3
	fill 4
	fill 5
	fill 6
	fill 7
	fill 8
	fill 9
	fill 10
	fill 11
	fill 12
	fill 13
	fill 14
	fill 15
	fill 16
	fill 17
	fill 18
	fill 19
	fill 20


        endmacro
	hide descriptor_init

; ****************************************************************************
; The following macro enables the user to easily configure a process to start
; at the INIT or CODE sections.  The first argument is an integer specifying
; the process to be reconfigured.  The second argument is the label of the new
; entry point. 


	macro process_start	n, where
	
	addr	process$(n)_header, d0	; Point d0 at the process header.
	add.a	5, d0	; Move down to the code pointer.
	addr	$where, c
	move.a	c, @d0		; Reconfigures process "n" to run from "where"
				; the next time it comes due.
	endmacro
	hide process_start

; ****************************************************************************
; The following macro enables the user to easily configure a process to start
; at a new label.  This macro takes no arguments, but expects that C.A contains
; a pointer to the new entry point.  This macro then  
; automatically changes the entry point of the current process to 
; the address in C.A.  It is expected that the process would load C.A with the
; address label, possibly with the ADDR macro.  The purpose behind this is to 
; allow the programmer to easily redirect the entry point of current process 
; without requiring the use of the IF_PROC/ENDIF_PROC construct.  This is 
; particularly advantageous when many processes share a section of code.  


	macro cur_process_start	
	
	PUSH.A 	C	; Save the pointer to the new entry point.
	CALL	GET_CURRENT_ID	; Get the process ID in A.B.  Trashes C.
	CLR.W	C	; 
	MOVE.B	A, C	; 	
	MOVE.W	C, A	; Zero out upper bits of A.
        ADD.A	C, C	; 
	ADD.A	C, C	;
	ADD.A	A, C	; ...and multiply by 5.

; C.A now contains the offset from beginning of the process descriptor list.


	ADDR	DESCRIPTOR_LIST, D0 ; Address of descriptor list. Trashes A.
	SWAP.A	A, D0
	ADD.A	C, A	; Add in the offset.  D0 points at process descriptor.
	SWAP.A	A, D0
	MOVE.A	@D0, C	; Get the descriptor (pointer to the process header).
	MOVE.A	C, D0	; Point D0 to the process header.

	add.a	5, d0	; Move down to the code pointer.
	POP.A	C	; Pop the new entry point back into C.
	move.a	c, @d0	; Reconfigures the current process to start at the
			; new entry point the next time it comes due.
	endmacro
	hide cur_process_start


;**************************************************************************
; The following macro is used by the MPE programmer to execute a certain piece
; of code conditioned on whether a specified process is current.

	macro	if_proc	n
	n=$n
	 		; Do the macro.
	save	nosym

	nosym = gensym

	swap.a	c, d0		; Save D0 in C.A
	addr	current_context_id, d0	; Point to current context id variable.
	move.b	@d0, a		; Get the current context id.
	swap.a	c, d0		; Restore old D0.
	move.p2	$(n), c		; 
	brne.b	c, a, $nosym	; Skip the code if specified process is not
				; current.
	endmacro
	hide if_proc	

	macro	endif_proc
$NOSYM:	; Execution jumps to here if the specified process was not current.

	  restore  nosym
	endmacro
	hide	endif_proc




	header


	code


; Descriptions of global RAM routines	

;	ADD_PROCESS	; Arguments: A.A contains the address of the process
			;            header of the process to be added.
			; 	     C.W contains the execution time.
	                  
;	RUN_IT		; Jumped-to from the SCHEDULER.  Runs the process 
			; whos ID is given in A.B.

; 	SAVE_CONTEXT	; Saves the context of the current process, as
			; indicated by the CURRENT_CONTEXT_ID variable.

; 	RESTORE_CONTEXT	; Restores the context of the process whos ID is given
			; in A.B.  This routine also leaves a pointer to the
			; code of the new process in C.A, so the calling
			; process can either jump to the new process, or just
			; use RESTORE_CONTEXT to copy the context of another
			; process to itself without actually transfering 
			; execution to the new process.


        jump	START	; Skip over all the global variables below and go
                        ; straight into the initialization code.


; ****************************************************************************
; The following state variables are intended to be used with the 
; SAVE_STATE and RESTORE_STATE routines.  These routines save the system
; registers into the following global state variables.  A and C are never
; saved or restored.  SAVE_STATE trashes A and C.  RESTORE_STATE trashes A, but
; preserves C. 

STATE_DATA1:	DATA.W	0, 0 
		DATA.5	0, 0	; Scratch storage for B, D, D0, D1 
				; This storage area can be used by each process
				; whenever some scratch storage is needed. This
				; storage is used by the SAVE_STATE and 
				; RESTORE_STATE routines.
; ****************************************************************************


; ****************************************************************************
; The following variable is used to hold the global time variable. 

CUR_TIME:	DATA.W	0		; Global variable to hold current time.
					; Set by scheduler.  Read by processes.
; ***************************************************************************


; ***************************************************************************
; The following storage areas are to hold the register context for each 
; process running in the system.

CURRENT_CONTEXT_ID:	DATA.B	0 ; Indicates the ID of the processor context 
				; we are currently in.  This variable will be 
				; used by the SAVE_CONTEXT and RESTORE_CONTEXT
				; routines. A zero identifies the scheduler.
				; The other processes are numbered 1-255.


;*************************************************************************** 
; The following is the memory area where the schedulers run list is kept. 
; Each entry consists of a one BYTE process ID (1-255; 0 is reserved for the
; scheduler), followed by a 13 nibble time value which represents when this
; process should be run. The scheduler will step down this list, comparing the
; current time value stored in the CUR_TIME variable to the execution time of
; each process in the run list.  When a process comes due, the scheduler will
; call the SWAP_CONTEXT  routine which will swap in the context of the due
; process and then jump to the address given in the process header.


	RUN_LIST_SIZE  = ^d10 	; There are 10 process slots.
RUN_LIST: 	DATA.B	0	; ID of a process.
		DATA.W	0	; Execution time of process. 
		DATA.B	0	; ID of a process.
		DATA.W	0	; Execution time of process. 
		DATA.B	0	; ID of a process.
		DATA.W	0	; Execution time of process. 
		DATA.B	0	; ID of a process.
		DATA.W	0	; Execution time of process. 
		DATA.B	0	; ID of a process.
		DATA.W	0	; Execution time of process. 
		DATA.B	0	; ID of a process.
		DATA.W	0	; Execution time of process. 
		DATA.B	0	; ID of a process.
		DATA.W	0	; Execution time of process. 
		DATA.B	0	; ID of a process.
		DATA.W	0	; Execution time of process. 
		DATA.B	0	; ID of a process.
		DATA.W	0	; Execution time of process. 
		DATA.B	0	; ID of a process.
		DATA.W	0	; Execution time of process. 



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

	gen_headers 0, scheduler	; Create scheduler header.
	gen_headers 1, process1	; Create process headers.
	gen_headers 2, process2
	gen_headers 3, process3
	gen_headers 4, process4
	gen_headers 5, process5 
	gen_headers 6, process6 
	gen_headers 7, process7 
	gen_headers 8, process8 
	gen_headers 9, process9 
	gen_headers 10, process10 
	gen_headers 11, process11 
	gen_headers 12, process12 
	gen_headers 13, process13 
	gen_headers 14, process14 
	gen_headers 15, process15 
	gen_headers 16, process16 
	gen_headers 17, process17 
	gen_headers 18, process18 
	gen_headers 19, process19 
	gen_headers 20, process20 

	gen_descriptor 0
	gen_descriptor 1
	gen_descriptor 2
	gen_descriptor 3
	gen_descriptor 4
	gen_descriptor 5
	gen_descriptor 6
	gen_descriptor 7
	gen_descriptor 8
	gen_descriptor 9
	gen_descriptor 10
	gen_descriptor 11
	gen_descriptor 12
	gen_descriptor 13
	gen_descriptor 14
	gen_descriptor 15
	gen_descriptor 16
	gen_descriptor 17
	gen_descriptor 18
	gen_descriptor 19
	gen_descriptor 20


START:	
;	move.a	pc, a		; debug
;	move.a	a, r0
;	call	save_registers
;	call	restore_registers
;	call	push_r0_short
;	call	save_registers


	call	save_registers
	header_init 0		; Fill in the scheduler header.
	header_init 1		; Fill in the process headers.
	header_init 2
	header_init 3
	header_init 4
	header_init 5
	header_init 6
	header_init 7
	header_init 8
	header_init 9
	header_init 10
	header_init 11
	header_init 12
	header_init 13
	header_init 14
	header_init 15
	header_init 16
	header_init 17
	header_init 18
	header_init 19
	header_init 20

; Now fill in the address pointers in the process descriptor list.

	descriptor_init

; Now we need to clear out the RUN_LIST so we know we are starting with
; a clean slate.

	move.p5	run_list_size, c	; Number of entries.
	addr	run_list, d0	; Point at top of RUN_LIST. Trashes A.
	CLR.W	A
CLR_RUN_LIST:
	MOVE.B	A, @D0
	ADD.A	^d2, d0
	add.a	^d16, d0
	dec.a	c
	brnz.a	c, clr_run_list	; Clear out all the process IDs from RUN_LIST.
	

; Now we need to make the scheduler the current process.

	ADDR	CURRENT_CONTEXT_ID, D0
	CLR.W	A
	MOVE.B	A, @D0	; Make scheduler the current context.



; ****************************************************************************
; NOTES for SCHEDULER:	

;	        R0	; Index into RUN_LIST (slot #), numbered 0 through N-1.
; 		R1	; Copy of the ID of the new process to be run.
;		R2	;
;		R3	;
;               R4	;

;		D0	; 
;		D1	; Address Pointer into RUN_LIST.

;		B	; Contains a local copy of the CUR_TIME variable. 
;		D	; Number of process slots in RUN_LIST.

; Now we need to start up the scheduler.  The scheduler will step down the run
; list, running each process as it comes due.  The process header
; will initially point to the init code for the process.  At the end of the 
; initialization phase, the user may mofify the process header to 
; point at the body of the process, where the real work is done.

	CALL	GET_TICKS	; Get the time into C.13.
	MOVE.P2	^d1, A		; Process ID.
	CALL	ADD_PROCESS	; Schedule process1 for immediate execution.

	MOVE.P2	RUN_LIST_SIZE, A	; Number of process slots in RUN_LIST.
	DEC.B	A		; Decrement this number by 1; 0 to (N-1) limit.
	MOVE.B	A, C		;
	MOVE.B	C, D		; Keep RUN_LIST_SIZE in D.
	CLR.W	A		; 
	MOVE.W	A, R0		; R0 holds current slot pointer.
	ADDR	RUN_LIST, C	; Get the address of RUN_LIST into C. 
	SWAP	C, D1		; Initialize RUN_LIST index to top of RUN_LIST.

	ADDR	CUR_TIME, D0	; Put the address of the CUR_TIME variable
				; in D0.  Trashes A.
	CALL	GET_TICKS	; Puts the value of ticks in C.13. Trashes A.
	MOVE.W	C, @D0		; Update the value of the CUR_TIME variable.
	MOVE.W	C, B		; Put a copy in B.  This is the local 
				; copy for the scheduler.	

	
; Now we need to step down the run list, checking the run time of each slot 
; which has a nonzero ID against the current time.  I will keep the run_list
; slot count in R0.  Whenever the scheduler is entered from the SCHEDULER entry
; point, the scheduler will pick up at the point in the run list where it left
; off.  For example, if the scheduler runs the process in slot #5, then when
; that process completes and returns to the SCHEDULER entry point, the next
; slot to be checked against the clock will be slot #6.  

SCHEDULER_CODE:
; This is the loop that scans the run list and runs the processes.
; This is the point where execution will return when a process completes.

; First, the scheduler needs to capture the current time, which will be used 
; to determine whether a given process is due.


	CLR.W	A		; Clear upper bits of A.
SCHED:	MOVE.B	@D1, A		; Get the process ID of this slot.
	BRNZ.B	A, GOOD_ID	; Is this a real process ID or is it zero?
	CALL	NEXT_SLOT	; Else point to the next slot in the RUN_LIST.
	JUMP	SCHEDULER_CODE	; Keep scanning the RUN_LIST for due processes.

GOOD_ID:	; Check the time and run the process if it is due.

	ADD.A	^d2, D1		; Point D1 at the execution time of this slot.
	MOVE.W	@D1, C		; Get the execution time for this process.
	SUB.A	^d2, D1		; Point back at ID; Required by NEXT_SLOT.
	BRGE.W	B, C, RUN_IT	; Run the process if it has come due.
				; The process ID is in A.B.
	CALL	NEXT_SLOT	; Point to the next slot in the RUN_LIST.
	JUMP	SCHEDULER_CODE	; Keep scanning the RUN_LIST for due processes.

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

NEXT_SLOT:	; Point to the next process in the RUN_LIST.

; This is the routine that advances the RUN_LIST slot counter (R0) and address
; pointer (D1) to the next slot in a wraparound fashion. 

	ADD.A	2, D1	; 
	ADD.A	16, D1	; Point D1 to the next ID in the RUN_LIST.
        MOVE.B	R0, A   ; Get the RUN_LIST slot counter.
	INC.B	A	; Indicate the next process slot.
	MOVE.B	A, R0	; Update R0 with the new slot count.
	MOVE.B	D, C	; Get the RUN_LIST index limit.
	BRGE.B	C, A, DONE_NEXT_SLOT	; If not end of list, then return.

; We have reached the end of the RUN_LIST, so we need to reset the RUN_LIST
; slot counter in R0 and the memory pointer in D1.

	CALL	GET_TICKS	; Puts the value of ticks in C.13. Trashes A.
	MOVE.W	C, B		; Put a copy in B.  This is the local 
				; copy for the scheduler.	
	CLR.B	A
	MOVE.B	A, R0	; Clear out the slot counter in R0.
	ADDR	RUN_LIST, C	; Get the address of RUN_LIST into C. 
	SWAP	C, D1		; Initialize RUN_LIST index to top of RUN_LIST.
DONE_NEXT_SLOT:
	RETCLRC	

; ****************************************************************************
RUN_IT:		; Run the process pointed to by A.B.

; This is the routine that runs the process pointed to by A.B.  This is done
; by getting the process header pointer from the process descriptor list. 
; The process header contains pointers to the process context and to the code
; for the process.  The process context is first restored, and then execution 
; jumps to the process code.

	MOVE.B	A, R1		; Save the new process ID for now.

	ADDR	CUR_TIME, D0	; Put the address of the CUR_TIME variable
				; in D0.  Trashes A.
	MOVE.W	B, C		; Time that was compared to time stamp.
	MOVE.W	C, @D0		; Update the value of the CUR_TIME variable.

	CLR.W	A
	MOVE.B	A, @D1		; Write a zero over the process ID in the 
				; RUN_LIST, thus deleting the process from the
				; RUN_LIST.
	CALL	NEXT_SLOT	; Point SCHEDULER to next slot in the RUN_LIST.
	CALL	SAVE_CONTEXT	; Uses CURRENT_CONTEXT_ID as a process ID to 
				; save the current register context to the 
				; appropriate place.  Does not save A or C.
	ADDR 	CURRENT_CONTEXT_ID, D0	; Point D0 at curr context variable.
					; Trashes A.
	MOVE.B	R1, A		; Restore the new process ID.
	MOVE.B	A, @D0		; Store the new process ID into the 
				; CURRENT_CONTEXT_ID variable.
	CALL	RESTORE_CONTEXT	; Restores the context of the new process, and
				; leaves a pointer to the new process code in
				; C.A. 



	JUMP	C		; Jump to new process code.

; ****************************************************************************
RESTORE_CONTEXT:
; This routine uses the context ID in A.B and restores that process context.
; The pointer to the code for the new process is left in C.A.

	CLR.W	C	; 
	MOVE.B	A, C	; 	
	MOVE.W	C, A	; Zero out upper bits of A.
        ADD.A	C, C	; 
	ADD.A	C, C	;
	ADD.A	A, C	; ...and multiply by 5.

; C.A now contains the offset from beginning of the process descriptor list.


	ADDR	DESCRIPTOR_LIST, D0 ; Address of descriptor list. Trashes A.
	SWAP.A	A, D0
	ADD.A	C, A	; Add in the offset.  D0 points at process descriptor.
	SWAP.A	A, D0
	MOVE.A	@D0, C	; Get the descriptor (pointer to the process header).


; Now that we have the descriptor (a pointer to the process header), we need
; to fetch the context pointer, restore the context, and then leave a pointer
; to the process code in C.A.

	MOVE.A	C, D0	; Point D0 to the process header.
	MOVE.A	@D0, A	; Get the address of the process context.
	ADD.A	^d5, D0	; Point D0 to the code pointer.
	MOVE.A	@D0, C	; Get the code pointer.
	PUSH.A	C	; Save the code pointer on the stack for now.
	MOVE.A	A, D0	; Point D0 to the context area. 
	MOVE.W	@D0, C		; 
	MOVE.W	C, B		; Restore B.
	ADD.A	^d16, D0
	MOVE.W	@D0, C		;
	MOVE.W	C, D		; Restore D.
	ADD.A	^d16, D0
	MOVE.A	@D0, C		; Restore D0 (in C for now).
	ADD.A	^d5, D0
	SWAP.A	D1, C           ; Save D0 contents into D1 for now.
	MOVE.A	@D0, C		; Get D1 contents into C.
	SWAP.A	D1, C 		; Restore D1. C holds restored D0 contents.
	ADD.A	^d5, D0
	MOVE.W	@D0, A		; 
	MOVE.W	A, R0		; Restore R0.
	ADD.A	^d16, D0
	MOVE.W	@D0, A		; 
	MOVE.W	A, R1		; Restore R1.
	ADD.A	^d16, D0
	MOVE.W	@D0, A		; 
	MOVE.W	A, R2		; Restore R2.
	ADD.A	^d16, D0
	MOVE.W	@D0, A		; 
	MOVE.W	A, R3		; Restore R3.
	ADD.A	^d16, D0
	MOVE.W	@D0, A		; 
	MOVE.W	A, R4		; Restore R4.
	SWAP.A	C, D0		; Restore D0.
	POP.A	C		; Pop the pointer to the process code in C.
	RET			; ...and return.

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

SAVE_CONTEXT:
; This routine uses the context ID in CURRENT_CONTEXT_ID and saves that 
; process context.
; Note that this routine makes sure to not alter the states of the system 
; registers or the general purpose registers in the process of saving them.
; Note also that A and C are not saved.  The contents of B, D, D0, D1, and 
; R0-R4 are preserved by this routine.

	CALL	GET_CURRENT_ID	; Get CURRENT_CONTEXT_ID into A.B
SAVE_CONTEXT_BY_A:	; This entry point allows the process to save the 
			; current context to that of the process given in A.B.
	CLR.W	C	; 
	MOVE.B	A, C	; 	
	MOVE.W	C, A	; Zero out upper bits of A.
        ADD.A	C, C	; 
	ADD.A	C, C	;
	ADD.A	A, C	; ...and multiply by 5 to give an offset into the
			; descriptor list.

; C.A now contains the offset from beginning of the process descriptor list.

	SWAP.A	A, D0	; Get a copy of D0 into A.
	SWAP.A	A, C	; Do not lose the descriptor offset (save it in A).
	PUSH.A	C	; Save old D0 on the return stack.
	SWAP.A	A, C	; Restore the descriptor offset into C.
	ADDR	DESCRIPTOR_LIST, D0 ; Address of descriptor list. Trashes A.
	SWAP.A	A, D0	; Put pointer to descriptor list into A.
	ADD.A	C, A	; Add in the offset.  
	MOVE.A	A, D0   ; D0 points at process descriptor.	
	MOVE.A	@D0, C	; Get the descriptor (pointer to the process header).
	

; Now that we have the descriptor (a pointer to the process header), we need
; to fetch the context pointer and save the process context.

	MOVE.A	C, D0	; Point D0 to the process header.
	MOVE.A	@D0, C	; Get the address of the context.
	MOVE.A	C, D0	; Point D0 to the context area. 
	MOVE.W	B, A		; 
	MOVE.W	A, @D0		; Save B.
	ADD.A	^d16, D0
	SWAP.W	D, C
	MOVE.W	C, @D0		; Save D.
	SWAP.W	D, C
	ADD.A	^d16, D0
	POP.A	C		; Recover the old D0 (pushed previously).
	MOVE.A	C, @D0		; Save D0. Leave copy of D0 in C.
	ADD.A	^d5, D0
	SWAP.A	D1, C		; 
	MOVE.A	C, @D0		; Save D1.
	SWAP.A	D1, C 		; Restore D1. copy of old D0 still in C.
	ADD.A	^d5, D0
	MOVE.W	R0, A		; 
	MOVE.W	A, @D0		; Save R0.
	ADD.A	^d16, D0
	MOVE.W	R1, A		; 
	MOVE.W	A, @D0		; Save R1.
	ADD.A	^d16, D0
	MOVE.W	R2, A		; 
	MOVE.W	A, @D0		; Save R2.
	ADD.A	^d16, D0
	MOVE.W	R3, A		; 
	MOVE.W	A, @D0		; Save R3.
	ADD.A	^d16, D0
	MOVE.W	R4, A		; 
	MOVE.W	A, @D0		; Save R4.
	SWAP.A	C, D0		; Restore D0.
	RET			; Clear carry bit and return.

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

ADD_PROCESS:
; This routine will add a process to the process list.  The basic method used
; is simply to insert the new process ID and execution time into the first 
; empty slot (defined as one which has a zero for its process ID).
; The process ID is expected in A.B, and the process execution time is expected
; in C.13.
;
; Note: This routine changes many of the system registers, as well as the 
;	general purpose registers.  For this reason, it is highly recommended
;	that the calling routine do a SAVE_CONTEXT before executing this 
;	routine, followed immediately by a RESTORE_CONTEXT or a jump to the
;	scheduler.

	MOVE.B	A, R2		; Save the new process ID.
	BRZ.B	A, BAD_PROC_ID	; Zero is not a valid process ID.
	MOVE.W	C, R3		; Save the execution time of the new process.
	MOVE.P2	NUM_OF_PROC, C	; Get the number of defined processes.
	BRGE.B	C, A, J_VPI ; Did the user pass in a valid ID?
	JUMP	BAD_PROC_ID	; If not then output the error information.
J_VPI:	JUMP	VALID_PROC_ID   ; Else schedule the process.
BAD_PROC_ID: 	CALL	RESTORE_REGISTERS
	MOVE.P5	^d98, C
	MOVE.A	C, R0
	CALL	PUSH_R0_SHORT	; Push an error code of 98 to indicate that
				; a process tried to schedule an invalid ID.
	CALL	SAVE_REGISTERS

	ADDR	CURRENT_CONTEXT_ID, D0
	CLR.W	C
	MOVE.B	@D0, C
	MOVE.W	C, R0           	; Push the ID of the process which 
	CALL	RESTORE_REGISTERS	; caused the error.
	CALL	PUSH_R0_SHORT	
	CALL	SAVE_REGISTERS
	

	CLR.W	A
	MOVE.B	R2, A
	MOVE.A	A, R0
	CALL	RESTORE_REGISTERS
	CALL	PUSH_R0_SHORT   ; Push the erroneous ID that the current
	CALL	SAVE_REGISTERS  ; process tried to schedule.
	JUMP	RR_RPLCONT	
	
VALID_PROC_ID:
	CLR.W	C	
	MOVE.P2	RUN_LIST_SIZE, C	; Number of process slots in RUN_LIST.
	DEC.B	C		; Decrement this number by 1; 0 to (N-1) limit.
	MOVE.W	C, D		; Keep RUN_LIST_SIZE limit in D.
	CLR.W	A		; 
	MOVE.W	A, R0		; R0 holds current slot pointer.
	ADDR	RUN_LIST, C	; Get the address of RUN_LIST into C. 
	SWAP.A	C, D1		; Initialize RUN_LIST index to top of RUN_LIST.

ADD_PROCESS_LOOP:
	MOVE.B	@D1, A		; Check for an empty slot.
	BRZ.B	A, FOUND_EMPTY	; 
	CALL	NEXT_SLOT	; Look at the next slot.
	MOVE.B	R0, A		; What slot are we looking at?
	BRNZ.B	A, ADD_PROCESS_LOOP ; Fall through the loop when every slot
				; is non-empty.
; If we fall through this loop, then we are in a serious error scenario, 
; because the process table has filled up and there are no empty slots.
; This is very bad.
	move.p5	^d99, a		; Error code for run_list overflow.
ERROR:	MOVE.A	A, R0
	CALL	RESTORE_REGISTERS
	CALL	PUSH_R0_SHORT 	; Push ^d99 error code to indicate a run-list
	CALL	SAVE_REGISTERS	; overflow.

	ADDR	CURRENT_CONTEXT_ID, D0
	CLR.W	C
	MOVE.B	@D0, C
	MOVE.W	C, R0           	; Push the ID of the process which 
	CALL	RESTORE_REGISTERS	; caused the error.
	CALL	PUSH_R0_SHORT	
	CALL	SAVE_REGISTERS

	JUMP	RR_RPLCONT	; Push the error code onto the stack and
				; kill the program.


FOUND_EMPTY:	; We have found an empty slot!  D1 should be pointing to the
		; empty slot, so all we have to do is fill it in.
	MOVE.B	R2, A	; Get the process ID.
	MOVE.B	A, @D1	; Write the ID into the slot.
	ADD.A	^d2, D1	; Point D1 at the execution time.
	MOVE.W	R3, C	; Get the process execution time.
	MOVE.W	C, @D1	; Write the execution time into the slot.
	RET		

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

TO_SCHEDULER:
; This routine is used by processes to tell the system to swap back to the 
; scheduler.  This routine just restores the scheduler context and jumps to 
; the scheduler.  It is up to each process to save its own context.

	ADDR	CURRENT_CONTEXT_ID, D0
	CLR.W	A
	MOVE.B	A, @D0	; Make scheduler the current context.
	CALL	RESTORE_CONTEXT	; Restore the context of the scheduler.
	JUMP	C	; Jump to the scheduler. 
	
	
; ****************************************************************************
SAVE_STATE: SWAP C, D0		; Save D0 in C for now.  Trashes C. 
	ADDR	STATE_DATA1, D0	; Point to STATE_DATA. Trashes A. 
	MOVE.W	B, A		; 
	MOVE.W	A, @D0		; Save B.
	ADD	16, D0
	SWAP.W	D, C
	MOVE.W	C, A		; 
	SWAP.W	D, C
	MOVE.W	A, @D0		; Save D.
	ADD	16, D0
	MOVE.A	C, @D0		; Save D0 (copied to C previously).
	ADD	5, D0
	SWAP	D1, C
	MOVE.A	C, @D0		; Save D1.
	SWAP	D1, C 		; Restore D1.
	SWAP	C, D0		; Restore D0.
	RETCLRC			; Clear carry bit and return.
	
RESTORE_STATE:
	ADDR	STATE_DATA1, D0	; Point to STATE_DATA. Trashes A.
	MOVE.W	C, A		; Save C in A for now.
	MOVE.W	@D0, C		; 
	MOVE.W	C, B		; Restore B.
	ADD	16, D0
	MOVE.W	@D0, C		; Restore D.
	MOVE.W	C, D		; Restore B.
	ADD	16, D0
	MOVE.A	@D0, C		; Restore D0 (in C for now).
	ADD	5, D0
	SWAP.A	D1, C           ; Save D0 contents into D1 for now.
	MOVE.A	@D0, C		; Get D1 contents into C.
	SWAP	D1, C 		; Restore D1.
	SWAP	C, D0		; Restore D0.
	MOVE.W	A, C		; Preserve the value of C saved at top.
	RETCLRC			; Clear carry bit and return.
	
GET_TICKS:
	CALL	SAVE_STATE	; Save system registers.
	CALL	ROM_GET_TICKS   ; Get the ticks value from hardware timer.
	CALL	RESTORE_STATE   ; Restore system registers.  Trashes A.
	RET			; Ticks is returned in C.13.


GET_CURRENT_ID:
	ADDR	CURRENT_CONTEXT_ID, C
	SWAP.A	C, D0		; Point D0 at CURRENT_CONTEXT_ID.
	MOVE.B	@D0, A
	SWAP.A	C, D0		; Restore D0.	
	RET

GET_CUR_TIME:
	ADDR	CUR_TIME, C
	SWAP.A	C, D0		; Point D0 at CUR_TIME.
	MOVE.B	@D0, A
	SWAP.A	C, D0		; Restore D0.	
	RET

; ****************************************************************************
; This is where the definition of the multiprocessing environment ends.
; ****************************************************************************
; 


; ****************************************************************************
; ****************************************************************************
; THE PROCESS DEFINITIONS START HERE.
; The code below comprises the process definitions for 7 processes.  Refer to 
; the MPE users guide for an explanation of what each process does, and how 
; the processes interact.
;

  
PROCESS1_DATA:
info:	data.a	^xAAAAA ; This is a simple data pattern for process 1.
	data.a	^xAAAAA
	data.a	^xAAAAA
	data.1	^xA	; This is broken up for users who lack GNU-C version
			; of star.

PROCESS1_INIT:

	process_start	1, process1_code ; reconfigures this process to start 
				; at the process1_code label from now on.

; This is process #1.  This process will kick off the keyboard scanner process
; (process #7) and then this process will perform an ongoing register integrity
; test.  This process basically implements a simple sanity check over the MPE 
; system and halts execution and pushes an error code onto the stack if it 
; detects a problem.  All this process really does is check that MPE is doing 
; the process context save/restore function properly.  This process runs rather 
; infrequently, so it does not add much overhead to the system.



	ADDR	CUR_TIME, D0
	MOVE.W	@D0, C		; Put CUR_TIME into C. 
	MOVE.P2	^d7, A		; Process 7 ID. (keyboard scanner)
	CALL	ADD_PROCESS     ; schedule process 7 for execution.

; Now we need to set up the registers so that each register contains a unique
; value.  Then these values will be checked each time this process is run.

	addr	info, d0
	move.w	@d0, a		; Now A contains all As.
	move.w	a, b
	inc.w	a
	move.w	a, c
	move.w	c, d
	inc.w	c
	move.a	c, d0
	inc.w	c
	move.a	c, d1
	inc.w	c
	move.w	c, r0
	inc.w	c
	move.w	c, r1
	inc.w	c
	move.w	c, r2
	inc.w	c
	move.w	c, r3
	inc.w	c
	move.w	c, r4


PROCESS1_CODE:
; This is the point to where execution will jump when the process is run.  The
; above initialization code is only executed the very first time this process
; is run.

	ADDR    INFO, c	; Trashes A, and C obviously.
	SWAP.A  C, D0   ; Point D0 at the data.
	MOVE.W  @D0, A
	SWAP.A  C, D0   ; Restore D0.
	MOVE.W  A, C    ; copy the data into c because only C can be compared
	                ; with D.
	BREQ.W  A, B, ok1	; Test B
	MOVE.P5 ^D0, A	; Error code 0 for register B. 
	JUMP    ERROUT	;
	
OK1:	
        INC.W   C        
        BREQ.W  D, C, OK2	; Test D.
        MOVE.P5 ^X1, A  ; Error code 1 for register D.    FAILED.
        JUMP    ERROUT  ;
        
OK2:    INC.W   A        
        INC.W   A        
        SWAP.A  C, D0    
        BREQ.A  C, A, OK3	; Test D0.
        MOVE.P5 ^X2, A  ; Error code 2 for register D0. 
        JUMP    ERROUT  ;
         
OK3:    INC.W   A        
        SWAP.A  C, D0   ; Restore D0 from the previous test.
        SWAP.A  C, D1   ; 
        BREQ.A  C, A, OK4	; Test D1.
        MOVE.P5 ^X3, A  ; Error code 3 for register D1.  FAILED.
        JUMP    ERROUT  ;
        
OK4:    SWAP.A  C, D1   ; Restore D1 from the previous test.
        INC.W   A        
        MOVE.W  R0, C    
        BREQ.A  C, A, OK5	; Test r0.
        MOVE.P5 ^X4, A  ; Error code 4 for register r0. 
        JUMP    ERROUT  ;
                         
OK5:    INC.W   A        
        MOVE.W  R1, C    
        BREQ.A  C, A, OK6	; Test r1.
        MOVE.P5 ^X5, A  ; Error code 5 for register r1. 
        JUMP    ERROUT  ;
        
OK6:    INC.W   A        
        MOVE.W  R2, C    
        BREQ.A  C, A, OK7	; Test r2.
        MOVE.P5 ^X6, A  ; Error code 6 for register r2. 
        JUMP    ERROUT  ;
                         
OK7:    INC.W   A        
        MOVE.W  R3, C    
        BREQ.A  C, A, OK8	; Test r3.
        MOVE.P5 ^X7, A  ; Error code 7 for register r3. 
        JUMP    ERROUT  ;
                         
OK8:    INC.W   A        
        MOVE.W  R4, C    
        BREQ.A  C, A, OK9	; Test r4.
        MOVE.P5 ^X8, A  ; Error code 8 for register r4. 
        JUMP    ERROUT  ;
        
OK9:    
	CALL	SAVE_CONTEXT
	ADDR	CUR_TIME, d0	; What time is it?
	MOVE.W	@D0, C
	CLR.W	A
	MOVE.P5 ^x1FFF, A
	ADD.W	A, C		; 
	MOVE.P2	^d2, A		; Process 2 ID.
	CALL	ADD_PROCESS     ; schedule process 2 for execution in 1 sec.
	JUMP	TO_SCHEDULER

ERROUT: MOVE.A  A, R0           ; Error code was placed in A above.
        CALL    RESTORE_REgisters
        CALL    PUSH_R0_SHort   ; Push the error code for the bad register.
        CALL    SAVE_REGISters
        JUMP    RR_RPLCONT	


; This concludes the definition of process #1.
; ****************************************************************************
; ****************************************************************************
; The definition of process #2 starts here.  The purpose of this process
; is just to clear out all of the process registers.  That way, if process
; #1 shows that the processor registers have the correct context for that
; process, then I know that the context save/restore by MPE was successfull.

PROCESS2_INIT:

	CLR.W	A
	MOVE.A	A, D0
	MOVE.A	A, D1
	MOVE.W	A, B
	MOVE.W	A, C
	MOVE.W	C, D
	MOVE.W	A, R0
	MOVE.W	A, R1
	MOVE.W	A, R2
	MOVE.W	A, R3
	MOVE.W	A, R4

	CALL	SAVE_CONTEXT
	ADDR	CUR_TIME, d0	; What time is it?
	MOVE.W	@D0, C
	MOVE.P2	^d1, A		; Process 1 ID.
        CALL	ADD_PROCESS     ; schedule process1 for immediate execution.
	JUMP	TO_SCHEDULER

; This concludes the definition of process #2.
; ****************************************************************************
; ****************************************************************************
; The following process definition is for processes 3#, #4, and #5.  The code
; differentiates between the three processes where necessary by using the
; IF_PROC and ENDIF_PROC macros, which are described in the users guide.


process3_init:
process4_init:
process5_init:

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

; This is the beginning of the bullet process.  The purpose of this
; process is to move an "F" through screen memory one pixel at a time.  This
; process will move the "F" one pixel to the right each time it is called.
; At the bottom of this process, it checks to see whether it has reached the
; end of screen memory, and if not it reschedules itself for future 
; execution based on the time inverval specified.  This process definition 
; differentiates between processes #3, #4, and #5 and reschedules these 
; different processes at different intervals.  This gives different speeds of 
; motion for the three "bullets" on the screen.

; The RPL from which MPE was called clears the screen and tells the display 
; controller to use PICT for the display (by doing a PVIEW command).


; First I need to modify the process header of this process so that execution
; will start at the PROCESS_CODE label in future invocations.

	addr	process3_code, C ; Get address of new entry point for this 
				; process.
	cur_process_start	; Reconfigures the current process to start
				; from the entry point given in C.A.

	MOVE.A 	PICT_POINTER, D0 ; Put the address of PICT in D0.
	MOVE.A	@D0, C		; Get the pointer to PICT.
	ADD.A	10, C		; Skip over prolog and size fields.
	ADD.A	10, C		; Skip over row and column fields.
	MOVE.A	C, D		; Save the address where the screen memory
				; starts.
	MOVE.P5	^d2174, A	; Number of nibbles - 2 in PICT.
	ADD.A	A, C		; A = address of last-1 nibble in PICT.
	MOVE.A	C, R2		; Save it in R2.



; Now stick an "F" at the beginning of the screen memory.


	CLR.W	A		; Clear register A.
	MOVE.B	A, B		; Clear out the bottom byte of B (bit counter).
	MOVE.P1	^xF, A		; Make A.A = F.
	MOVE.A	D, C		; Get the pointer into PICT.
	MOVE.A	C, D0		; Point D0 at PICT.
	MOVE.1	A, @D0		; Set the first nibble of the screen to be F.

PROCESS3_CODE:
PROCESS4_CODE:
PROCESS5_CODE:


; Now what we want to do is to move the "F" one pixel at a time through the
; screen memory.  I think I'll do it the easy way, by keeping track of the
; counter in B.B and just doing compares on that; there are only 4 cases to
; test.


	INC.1	B		; Increment the bit counter to the next value.

	MOVE.A	D, C		; Get pointer into PICT.
	SWAP.A	C, D1		; Point D1 into PICT.
	CLR.B	A		; Use A.B to test the current value of the bit
				; counter.
	BREQ.1	A, B, ZERO	; 
	INC.1	A   
	BREQ.1	A, B, ONE
	INC.1	A
	BREQ.1	A, B, TWO
THREE:	MOVE.P1	^x8, A		; Put New bit pattern of nibble  in A.
	INC.1	P		; 
	MOVE.P1	^x7, A		; Put new bit pattern of nibble+1 in A. 
	MOVE.1	0, P		; Zero out the Pointer register.
	MOVE.2	A, @D1		; Write out the two data values to the display.
	INC.A	D		; Increment the PICT pointer.
	MOVE.P2	^xF, C		; 
	MOVE.B	C, B		; So B resets to 0 on next iteration.
        JUMP DOIT 
ZERO:	
	MOVE.A	D, C		; Get the pointer into PICT.
	DEC.A	C		; point to the nibble we just left.
	CLR.B	A	
	SWAP	C, D1		; Point D1 to the old PICT nibble,
	MOVE.1	A, @D1		; and clear it.
	SWAP	C, D1		; Restore the PICT pointer in D1.
	MOVE.P1	^xF, A		; Put New bit pattern of nibble  in A.
	INC.1	P		; 
	MOVE.P1	^x0, A		; Put new bit pattern of nibble+1 in A. 
	MOVE.1	0, P		; Zero out the Pointer register.
	MOVE.2	A, @D1		; Write out the two data values to the display.
        JUMP DOIT 
ONE:	MOVE.P1	^xE, A		; Put New bit pattern of nibble  in A.
	INC.1	P		; 
	MOVE.P1	^x1, A		; Put new bit pattern of nibble+1 in A. 
	MOVE.1	0, P		; Zero out the Pointer register.
	MOVE.2	A, @D1		; Write out the two data values to the display.
        JUMP DOIT
TWO: 	MOVE.P1	^xC, A		; Put New bit pattern of nibble  in A.
	INC.1	P		; 
	MOVE.P1	^x3, A		; Put new bit pattern of nibble+1 in A. 
	MOVE.1	0, P		; Zero out the Pointer register.
	MOVE.2	A, @D1		; Write out the two data values to the display.
        
DOIT:	
	MOVE.A	R2, C		; Get the address of the last nibble in PICT.	
	BRLT.A	D, C, CONTINUE	; If we havent written the last location, then
				; we should reschedule this process for future
				; execution.
	JUMP	rr_rplcont	; Else, we can just exit.


CONTINUE: 

	CALL	SAVE_CONTEXT	; Save the context for this process.

	ADDR	CUR_TIME, D0	; Get the current run-time.
	MOVE.W	@D0, C
	MOVE.W	C, R0		; Save it in R0 for now.

	IF_PROC 3
	  CLR.W	A
	  MOVE.P5 ^x11F, A	; Reschedule interval for process 3.
	  JUMP	PROCESS3_CONT2
	ENDIF_PROC

	IF_PROC	4
	  CLR.W	A
	  MOVE.P5 ^x9F, A	; Reschedule interval for process 4.
	  JUMP	PROCESS3_CONT2
	ENDIF_PROC

	IF_PROC	5
	  CLR.W	A
	  MOVE.P5 ^xDF, A	; Reschedule interval for process 5.
	  JUMP	PROCESS3_CONT2
	ENDIF_PROC

PROCESS3_CONT2:	MOVE.W	R0, C           ; The current run-time.
	ADD.W	A, C		; Next time that the hyphen should move.
	MOVE.W	C, R0		; Save it in R0.

	CALL	GET_CURRENT_ID	; Get process ID into A.B. Trashes C.

	MOVE.W	R0, C		; Next time process should be run.
	CALL	ADD_PROCESS	; Schedule this process to run at the
				; time given in C.A. 	

	JUMP	TO_SCHEDULER	; Go back to the scheduler.


; The process definition for processes #3, #4, and #5 ends here.
; *************************************************************************
; *************************************************************************

; The definition of process #6 starts here.  This process is very similar to 
; the above definition for processes #3, #4, and #5.  Like the above
; definition, process #6 will also move a "bullet" through screen memory.  The 
; difference is that this process will schedule itself based on absolute time, 
; using the hardware timer to keep track of where the "bullet" should be on the
; screen, and then catching itself up to that point.  In this manner, this 
; process will not slow down as the system becomes heavily loaded, but rather
; it will use a constant amount of CPU time on the average, regardless of how
; many processes are running on the system. 

PROCESS6_INIT:
	process_start	6, PROCESS6_CODE


	MOVE.A 	PICT_POINTER, D0 ; Put the address of PICT in D0.
	MOVE.A	@D0, C		; Get the pointer to PICT.
	ADD.A	10, C		; Skip over prolog and size fields.
	ADD.A	10, C		; Skip over row and column fields.
	MOVE.A	C, D		; Save the address where the screen memory
				; starts.
	MOVE.P5	^d2174, A	; Number of nibbles - 2 in PICT.
	ADD.A	A, C		; A = address of last-1 nibble in PICT.
	MOVE.A	C, R2		; Save it in R2.

	CLR.W	A
	MOVE.W	A, R0
	ADDR	CUR_TIME, D0
	MOVE.W	@D0, C
	MOVE.W	C, R0     	; Save time of first invocation of process.


; Now stick an "F" at the beginning of the screen memory.


	CLR.W	A		; Clear register A.
	MOVE.B	A, B		; Clear out the bottom byte of B (bit counter).
	MOVE.P1	^xF, A		; Make A.A = F.
	MOVE.A	D, C		; Get the pointer into PICT.
	MOVE.A	C, D0		; Point D0 at PICT.
	MOVE.1	A, @D0		; Set the first nibble of the screen to be F.

PROCESS6_CODE:


; Now what we want to do is to move the "F" one pixel at a time through the
; screen memory.  I think I'll do it the easy way, by keeping track of the
; counter in B.B and just doing compares on that; there are only 4 cases to
; test.

PROCESS6_LOOP:

	INC.1	B		; Increment the bit counter to the next value.

	MOVE.A	D, C		; Get pointer into PICT.
	SWAP.A	C, D1		; Point D1 into PICT.
	CLR.B	A		; Use A.B to test the current value of the bit
				; counter.
	BREQ.1	A, B, process6_ZERO	; 
	INC.1	A   
	BREQ.1	A, B, process6_ONE
	INC.1	A
	BREQ.1	A, B, process6_TWO
PROCESS6_THREE:	MOVE.P1	^x8, A		; Put New bit pattern of nibble  in A.
	INC.1	P		; 
	MOVE.P1	^x7, A		; Put new bit pattern of nibble+1 in A. 
	MOVE.1	0, P		; Zero out the Pointer register.
	MOVE.2	A, @D1		; Write out the two data values to the display.
	INC.A	D		; Increment the PICT pointer.
	MOVE.P2	^xF, C		; 
	MOVE.B	C, B		; So B resets to 0 on next iteration.
        JUMP process6_DOIT 
PROCESS6_ZERO:	
	MOVE.A	D, C		; Get the pointer into PICT.
	DEC.A	C		; point to the nibble we just left.
	CLR.B	A	
	SWAP	C, D1		; Point D1 to the old PICT nibble,
	MOVE.1	A, @D1		; and clear it.
	SWAP	C, D1		; Restore the PICT pointer in D1.
	MOVE.P1	^xF, A		; Put New bit pattern of nibble  in A.
	INC.1	P		; 
	MOVE.P1	^x0, A		; Put new bit pattern of nibble+1 in A. 
	MOVE.1	0, P		; Zero out the Pointer register.
	MOVE.2	A, @D1		; Write out the two data values to the display.
        JUMP process6_DOIT 
PROCESS6_ONE:	MOVE.P1	^xE, A		; Put New bit pattern of nibble  in A.
	INC.1	P		; 
	MOVE.P1	^x1, A		; Put new bit pattern of nibble+1 in A. 
	MOVE.1	0, P		; Zero out the Pointer register.
	MOVE.2	A, @D1		; Write out the two data values to the display.
        JUMP process6_DOIT
PROCESS6_TWO: 	MOVE.P1	^xC, A		; Put New bit pattern of nibble  in A.
	INC.1	P		; 
	MOVE.P1	^x3, A		; Put new bit pattern of nibble+1 in A. 
	MOVE.1	0, P		; Zero out the Pointer register.
	MOVE.2	A, @D1		; Write out the two data values to the display.
        
PROCESS6_DOIT:	
	MOVE.A	R2, C		; Get the address of the last nibble in PICT.	
	BRLT.A	D, C, PROCESS6_CONTINUE	
				; If we havent written the last location, then
				; we should reschedule this process for future
				; execution.
	JUMP	rr_rplcont	; Else, we can just exit.


PROCESS6_CONTINUE: 

	CLR.W	A
	MOVE.P5	^x9F, A  
	MOVE.W	R0, C           ; The last scheduled run-time.
	ADD.W	C, A		; Next time that the hyphen should move.
	MOVE.W	A, R0           ; Save next run-time.
	CALL	GET_TICKS	; Current time.
	MOVE.W	R0, A		; Get next run-time.
	BRGE.W	C, A, PROCESS6_LOOPER	; Run it again if it is time.

	CALL	SAVE_CONTEXT	; Save the context for this process.


	MOVE.W	R0, C		; Next time process should be run.
	CLR.W	A
	MOVE.P2	^d6, A		; ID for this process into A.B.
	CALL	ADD_PROCESS	; Schedule this process to run at the
				; time given in C. 	
	JUMP	TO_SCHEDULER	; Go back to the scheduler.


PROCESS6_LOOPER: JUMP	PROCESS6_LOOP

;**************************************************************************
;**************************************************************************
; This process is responsible for reading the keyboad, and spawning a new
; processes whenever the [1] key is pressed.  All other key presses are 
; discarded without action.
; This process will schedule processes #3, #4, #5, and #6 in that sequence.  
; One processes will be spawned for each press of the [1] key.  This process
; does one scan of the key buffer on each invocation.  After process #6 is 
; scheduled, this process ceases to reschedule.
 
PROCESS7_INIT:
	process_start 7, process7_code
        move.p2	^d2, C		; Pointer to the next-1 bullet process.
	MOVE.B	C, B            ; Save it in B.
	MOVE.P2	^d6, C          ; Upper process ID limit.
	MOVE.B	C, D            ; Save it in D.
 
PROCESS7_CODE:
	call	kb_poll			; Get key, if any
	brcs	process7_check_key
	CALL	SAVE_CONTEXT
	jump	process7_sched          ; If not then reschedule.

PROCESS7_CHECK_KEY:
	move.p2	^d49, C			; Scan code for the [+] key.
	breq.b	c, a, process7_valid_key	; Did user press [+]?
	MOVE.P2	^d47, C
	BREQ.B	C, A, PROCESS7_ABORT
	CALL	SAVE_CONTEXT
	jump	process7_sched          ; If not then reschedule.

PROCESS7_ABORT:
; The user has pressed the [.] key, so we want to abort the application.
	JUMP	RR_RPLCONT	; This kills the application and continues
				; with the RPL thread.

PROCESS7_VALID_KEY:			; The user pressed the [+] key!
	INC.B	B			; Point to the next bullet ID.
	MOVE.B	B, A			; Next process ID to start.
	MOVE.B	A, R0                   ; Save it in R0 for later (see below).
	MOVE.B	D, C                    ; Upper process ID limit.
	BRGE.B	C, A, PROCESS7_SPAWN	; If process number <=6 then run it.
	DEC.B	B			; Else un-increment B,
	CALL	SAVE_CONTEXT		; save the process context and 
	JUMP	PROCESS7_SCHED		; reschedule the process.

PROCESS7_SPAWN:
	CALL	SAVE_CONTEXT
	ADDR	CUR_TIME, D0		; Trashes A.
	MOVE.B	R0, A
	MOVE.W	@D0, C
	CALL	ADD_PROCESS

PROCESS7_SCHED:
; The current context of this process should have been saved by now. 

	ADDR	CUR_TIME, D0	; Point to the CUR_TIME variable.
	MOVE.W	@D0, C		; Get the current time.
	CLR.W	A
	MOVE.P3	^x3FF, A	; Reschedule this process to look at the
				; keyboard roughly 8 times per second.
	ADD.W	A, C	
	MOVE.P2	^d7, A		; ID for this process.
	CALL	ADD_PROCESS

PROCESS7_EXIT:
	JUMP	TO_SCHEDULER	; Return control to the scheduler.


;; Poll keyboard. System-based version. Returns the scan code in A.A.
;; Trashes C.A, B.B, and D0. The C bit is set if a key was pressed,
;; otherwise it's cleared.  This keyboard polling software was written
;; by Jan Brittenson (J.E.)
;;
;; This is a mutation of the ROM prefixed machine code routine to
;; get/peek the next entry in the keyboard buffer.
 
 
kb_poll:
	move.5	keybuf+1, d0	; KB Put ptr
	move.s	@d0, a		; A.S = put ctr
	dec	d0
	move.s	@d0, c		; C.S = get ctr
	breq.s	c, a, $100	; Ctrs are equal - buffer empty
 
	move	c.15, p		; P = get ctr
	inc.s	c		; Remove key
	move.s	c, @d0
 
	swap	c, d0
	add	p+1, c
	add	p+1, c		; C += get ctr, in bytes
	clr	p
	move	c, d0		; D0 = &next key
	clr.a	a
	move.b	@d0, a		; A.A = key
	retsetc
$100:
	clr.a	a
	retclrc
; ************************************************************************** 
 

	endcode

end